c++

Crypto++中的padding

c++

Posted by elmagnifico on January 8, 2021

Foreword

Crypto++中的pading似乎有点奇怪的问题,这里记录一下

Pading

首先知道,既然要加密,加密都是一定长度进行加密,和你的组大小有关系,一般都是16字节,那么内容不足16字节的时候就必须要填充一些内容进去,那么这里就需要padding了,具体怎么填充,不同的协议有不同的要求。

这里是Crypto的源码中关于padding模式的枚举

struct BlockPaddingSchemeDef
{
	/// \enum BlockPaddingScheme
	/// \brief Padding schemes used for block ciphers.
	/// \details DEFAULT_PADDING means PKCS_PADDING if <tt>cipher.MandatoryBlockSize() > 1 &&
	///   cipher.MinLastBlockSize() == 0</tt>, which holds for ECB or CBC mode. Otherwise,
	///   NO_PADDING for modes like OFB, CFB, CTR, CBC-CTS.
	/// \sa <A HREF="http://www.weidai.com/scan-mirror/csp.html">Block Cipher Padding</A> for
	///   additional details.
	/// \since Crypto++ 5.0
	enum BlockPaddingScheme {
		/// \brief No padding added to a block
		/// \since Crypto++ 5.0
		NO_PADDING,
		/// \brief 0's padding added to a block
		/// \since Crypto++ 5.0
		ZEROS_PADDING,
		/// \brief PKCS padding added to a block
		/// \since Crypto++ 5.0
		PKCS_PADDING,
		/// \brief 1 and 0's padding added to a block
		/// \since Crypto++ 5.0
		ONE_AND_ZEROS_PADDING,
		/// \brief W3C padding added to a block
		/// \sa <A HREF="http://www.w3.org/TR/2002/REC-xmlenc-core-20021210/Overview.html">XML
		///   Encryption Syntax and Processing</A>
		/// \since Crypto++ 6.0
		W3C_PADDING,
		/// \brief Default padding scheme
		/// \since Crypto++ 5.0
		DEFAULT_PADDING
	};
};

这里的DEFAULT_PADDING非常特殊,当时CBC或者ECB的情况下,只要分组大小时大于1并且剩余字节等于0的情况下,那么就用PKCS的模式,否则就都用无填充。实测CBC的情况下,使用的是PKCS模式。

PKCS7

填充的字符是相同的,这个字符是剩余要填充的值,比如要填充4个字节,那么这个填充的值就是4

例子:

假设字符源串16进制为 : 
DD DD DD DD DD DD DD DD | DD DD DD DD
那么padding后为:
DD DD DD DD DD DD DD DD | DD DD DD DD | 04 04 04 04 |

这样填充完成以后才会进行加密操作。

但是有个特殊情况,比如加密长度刚好是16的整数倍,那么这个时候依然要填充,这时填充值就是10了

PKCS5

和7不同的是,PKCS5固定填充长度为8字节,但是实际处理的时候8字节太慢了,这个时候需要其他长度,因而扩展出了PKCS7,PKCS7可以指定1-255内任何长度的包,剩下的填充规则相同

zeros

顾名思义,就是填充0即可,但是这种方法,显然只适用于有限的情况,很多数据中可能出现0,那么使用zero填充就会出错,他只适合string类型的填充

None或者No_padding

不填充

ISO 10126 与 ANSI X9.23

填充随机值,但是最后一字节永远是填充的长度,下面是一个包8字节,4字节需要填充

| DD DD DD DD DD DD DD DD | DD DD DD DD : 81 A6 23 04 |

不同的地方是ANSI X9.23的协议中很多实现是填充的0,而不是真随机值

| DD DD DD DD DD DD DD DD | DD DD DD DD : 00 00 00 04 |

在Crypto++中W3C_PADDING就是这种方法

ISO/IEC 7816-4

他是强制填充一字节的80,然后后面跟00,当然这种模式下这个80相当于不能是内容的结尾字符,否则会出错

其实这里填充的内容最小单元是以bit来算的,他是第一个bit填1,其余全填0,就变成了从80开始,后面全0的情况

比如8字节的包,这里需要填充4字节

| DD DD DD DD DD DD DD DD | DD DD DD DD 80 00 00 00 |

比如8字节的包,这里需要填充1字节

| DD DD DD DD DD DD DD DD | DD DD DD DD DD DD DD 80 |

在Crypto++中ONE_AND_ZEROS_PADDING就是指代这种模式

Block Cipher Modes

Symmetric block cipher modes fall into two categories: block-oriented and byte-oriented. For block-oriented modes, the plaintext or ciphertext can only be processed one block at a time. This means that a “padding scheme” is needed to specify how to handle the last block of a message, i.e. the standard algorithm name will be of the form “cipher/mode/padding”. If messages are always an exact number of blocks in length, “NoPadding” may be specified as the padding scheme.

For byte-oriented modes, the plaintext or ciphertext can be processed one byte at a time (by use of internal buffering if necessary). This means that a padding scheme is not required, i.e. the standard name will be of the form “cipher/mode”. For backward compatibility, however, any of the forms “cipher/mode”, “cipher/mode/NoPadding”, and “cipher/mode/NONE” should be accepted when the mode is byte-oriented. The last two of these are deprecated.

Crypto中关于组填充的模式中有这样的说明,如果是字节流模式的情况下,密文内容可以被逐字节处理,这种情况下,是不需要padding的。但是为了兼容其本身接口上还是保留了padding

指定填充模式

之前使用的时候,没有注意到这个padding模式,导致我没有填这个值,直接使用了默认值。

bool CBC_AESEncryptFile(std::string aesKey, std::string sIV, const std::string & input_filename,const std::string & output_filename)
{
	try
	{
		// first set aes key
		SecByteBlock key(AES::MAX_KEYLENGTH);  
		memset(key,0x30,key.size() );  
		aesKey.size()<=AES::MAX_KEYLENGTH?memcpy(key,aesKey.c_str(),aesKey.size()):memcpy(key,aesKey.c_str(),AES::MAX_KEYLENGTH);  

		// set iv
		byte iv[AES::BLOCKSIZE];  
		memset(iv,0x30,AES::BLOCKSIZE); 
		sIV.size()<=AES::BLOCKSIZE?memcpy(iv,sIV.c_str(),sIV.size()):memcpy(iv,sIV.c_str(),AES::BLOCKSIZE);

		// create a ase encryption
		AES::Encryption aesEncryption((byte *)key, AES::MAX_KEYLENGTH);  
		CBC_Mode_ExternalCipher::Encryption cbcEncryption(aesEncryption, iv);  

		// set the filter
		//StreamTransformationFilter ecbEncryptor(ecbEncryption, new HexEncoder(new StringSink(outstr))); 
		FileSource(input_filename.c_str(), true,new StreamTransformationFilter(cbcEncryption,new FileSink(output_filename.c_str())));
		return true;
	}
	catch (...)
	{
		return false;
	}
}

给进去的文件大小是不满16字节的,给出来的直接是16的整数倍,而当给出16字节的文件的时候,使用pkcs就直接解不开了,反而使用不填充可以解开

		BlockPaddingSchemeDef::BlockPaddingScheme padding = BlockPaddingSchemeDef::BlockPaddingScheme::PKCS_PADDING;
		FileSource(input_filename.c_str(), true,new StreamTransformationFilter(cbcDecryption,new FileSink(output_filename.c_str()),padding));

正确的做法,增加padding参数。

C#官方库解密bug

Crypto++使用pkcs的情况下,加密后的文件,给c#进行解密,有小概率会出现错误,而出错的地方都非常一致。在C++下使用Crypto,加解密没报错,也没有异常,但是C#中就会出现解密后,释放资源异常。

而出现这个问题的文件都有同一个填充情况

文件一结尾:
09 00 00 00 00 00 00 00 20 00 80 81 00 00 00 00 64 72 61 6D 61 66 69 6C 65 55 54 05 00 07 58 24 43 5F 50 4B 05 06 00 00 00 00 01 00 01 00 40 00 00 00 F3 7F 01 00 00 00 07 07 07 07 07 07 07

文件二结尾:
81 00 00 00 00 64 72 61 6D 61 66 69 6C 65 55 54 05 00 07 8D 12 F9 5F 50 4B 05 06 00 00 00 00 01 00 01 00 40 00 00 00 D4 AF 00 00 00 00 06 06 06 06 06 06

上面是两个都是出错的文件,一个是07结尾,一个是06结尾,在C#官方库中解密后都会出现释放异常。

而其他填充的结尾则不会出现这个情况,应该是官方库有某种bug导致的,具体原因未知,当c#指定zeropadding的时候反倒是能解开这个pkcs的padding,非常奇怪。

实际上测试的时候也发现了,其他的填充模式竟然也能互相解析。

比如,部分zero可以用pkcs解析,nopadding可以用zero解析,反之同理,但是需要注意的是这样解开的文件,可能还存有之前加密时留下的填充内容,需要单独处理。

而由于我们是在zip的基础上在进行压缩的,而zip本身的格式中带有标准的结束符,从而在重建zip对象的时候自动去除了尾部的无意义填充,从而无视了最后的填充值,直接解开了加密文件,但是如果是string类型或者其他数据流就不能这样用了,必须是什么padding用什么。

Summary

奇怪的问题

Quote

https://www.cnblogs.com/AloneSword/p/3491466.html

https://www.cnblogs.com/solohac/p/4284424.html

https://en.wikipedia.org/wiki/Padding_(cryptography)

https://tools.ietf.org/html/rfc2315

https://tools.ietf.org/html/rfc2898