1、SM4 CTS 说明
SM4的CTS模式(Cipher Text Stealing)是一种特殊的加密模式,它主要用于处理数据长度不是加密算法块长度整数倍的情况。这种模式在保证数据安全性的同时,也保证了加密后的数据长度与原始数据长度一致,这在某些应用场景中非常重要,如文件加密。
CTS模式是建立在CBC(Cipher Block Chaining)模式之上的,它通过“窃取”(stealing)前一个密文块的一部分数据来解决最后一个数据块长度小于加密算法块长度的问题。
2、SM4 CTS 加密过程
2.1 数据分块
首先将数据分为若干块,块大小通常是加密算法的块大小(对于SM4来说是128位或16字节)。
2.2 处理最后两个块:
1)如果数据的总长度正好是块大小的整数倍,直接使用CBC模式进行加密。
2)如果最后一个数据块不足一个完整块的长度,那么取倒数第二个完整的数据块和最后一个不完整的数据块进行特殊处理。
2.3 特殊处理:
1)使用CBC模式加密除了最后两个块的所有块。
2)对倒数第二个块进行加密。
3)将不完整的最后一个块填充至完整块长,填充内容临时借用倒数第二个密文块的相应部分。
4)加密处理后的这个“合成的完整块”。
5)将加密后的结果中对应原始不足块长度的部分放回到倒数第二个密文块的末尾。
2.4 输出结果:
1)输出除了最后两个块外的所有已加密块。
2)按新的顺序输出最后两个处理过的块。
2.5 加密示例代码
int sm4_cts_encrypt(const uint8_t *plaintext, uint8_t *ciphertext, size_t length, const uint8_t key[16], const uint8_t iv[SM4_BLOCK_SIZE]) {
unsigned char out_block[SM4_BLOCK_SIZE], iv_buf[SM4_BLOCK_SIZE];
memcpy(iv_buf, iv, SM4_BLOCK_SIZE);
int i=0;
SM4_KEY ks={0};
if (length <= 16)
return 0;
int ret = ossl_sm4_set_key(key,&ks);
if(ret!=1)
{
printf("ossl_sm4_set_key err %08x\n",ret);
return ret;
}
size_t residue = length % SM4_BLOCK_SIZE;
if (residue == 0) residue = SM4_BLOCK_SIZE;
for (i = 0; i < length/SM4_BLOCK_SIZE; i++) {
xor_blocks(plaintext + i * SM4_BLOCK_SIZE, iv_buf, out_block,SM4_BLOCK_SIZE);
ossl_sm4_encrypt(out_block, ciphertext + i * SM4_BLOCK_SIZE, &ks);
memcpy(iv_buf, ciphertext + i * SM4_BLOCK_SIZE, SM4_BLOCK_SIZE);
}
memcpy(out_block,iv_buf,SM4_BLOCK_SIZE);
xor_blocks(plaintext + i * SM4_BLOCK_SIZE, iv_buf, out_block,residue);
ossl_sm4_encrypt(out_block, out_block, &ks);
memcpy(ciphertext +length - residue, ciphertext +length - SM4_BLOCK_SIZE - residue, residue);
memcpy(ciphertext +length - residue - SM4_BLOCK_SIZE, out_block, SM4_BLOCK_SIZE);
return length;
}
3、SM4 CTS 解密过程
CTS模式的解密过程需要逆向执行加密过程中的所有步骤,包括正确处理最后两个密文块的数据交换和恢复原始数据块。
3.1 解密大部分块
1)使用标准的CBC模式解密所有除了最后两个块之外的密文块。
3.2 处理最后两个块
1)首先,需要确定最后两个块的特殊处理方式。这通常涉及到解析最后一个块,判断其长度是否等于标准块大小。
2)如果最后一个块不是完整的块,则从倒数第二个密文块中“窃取”足够的数据以补全这个块。
3.3 解密倒数第二个块
1)临时保存倒数第二个块的密文。
2)将这个块解密,得到明文。此时,解密得到的明文实际上包含了部分明文数据和用于填充最后一个块的数据。
3.4 解密合成的最后一个完整块
1)通过将最后一个不完整块和借用的数据合成一个完整块。
2)使用SM4解密这个合成的完整块,得到明文。
3.5 恢复最后两个块的正确数据
1)将解密得到的倒数第二个块的明文与最后一个块解密前合成的完整块中的原始数据进行必要的XOR操作,以恢复出原始的明文数据。
2)注意,这一步骤需要精确控制数据的交换和替换,确保明文的正确性。
3.6 输出所有明文块:
1)输出所有已解密的明文块,包括恢复的最后两个块。
3.7 解密示例代码
int sm4_cts_decrypt(const uint8_t *ciphertext, uint8_t *plaintext, size_t length, const uint8_t key[16], const uint8_t iv[SM4_BLOCK_SIZE]) {
unsigned char out_block[SM4_BLOCK_SIZE];
unsigned char iv_buf[SM4_BLOCK_SIZE];
memcpy(iv_buf, iv, SM4_BLOCK_SIZE);
size_t i;
SM4_KEY ks={0};
union {
size_t align;
unsigned char c[32];
} tmp;
if (length <= 16)
return 0;
size_t residue = length % SM4_BLOCK_SIZE;
if (residue == 0)
residue = SM4_BLOCK_SIZE;
length -= (16 + residue);
int ret = ossl_sm4_set_key(key,&ks);
if(ret!=1)
{
printf("ossl_sm4_set_key err %08x\n",ret);
return ret;
}
for ( i = 0; i < length; i += SM4_BLOCK_SIZE) {
ossl_sm4_decrypt(ciphertext + i, out_block, &ks);
xor_blocks(out_block, iv_buf, plaintext + i,SM4_BLOCK_SIZE);
memcpy(iv_buf, ciphertext + i, SM4_BLOCK_SIZE);
}
ossl_sm4_decrypt(ciphertext + i, tmp.c+SM4_BLOCK_SIZE, &ks);
memcpy(tmp.c, tmp.c + 16, 16);
memcpy(tmp.c, ciphertext - residue, residue);
ossl_sm4_decrypt(tmp.c, tmp.c, &ks);
int n =0;
for (n = 0; n < 16; ++n) {
unsigned char c = ciphertext[length+n];
plaintext[length+n] = tmp.c[n] ^ iv_buf[n];
iv_buf[n] = c;
}
for (residue += 16; n < residue; ++n)
plaintext[length+n] = tmp.c[n] ^ ciphertext[length+n];
return length+16+residue;
}
4、优点与局限
1)优点:
CTS模式能够处理任意长度的数据,且加密后的数据长度不变,这对于需要保持数据格式的应用非常有用。
2)局限:
CTS模式依赖于CBC模式,因此它继承了CBC模式的所有缺点,包括对初始向量(IV)的依赖以及潜在的安全隐患(如如果使用不当,可能会有安全风险)。
CTS模式适用于数据长度可能变动,且要求输出数据长度与输入相同的场合。在实际应用中,正确实现CTS模式需要仔细处理数据块的拆分和重组,确保在各种情况下都能安全、准确地加解密。