【工程实践】Node.js与C++ AES算法互通

(点击上方公众号,可快速关注)

近期一个涉及与外部渠道对接的项目,对方是Node.js编写的后台服务,我们拿到对方通过AES192算法生成的密文,需要解密验证。由于我们的服务后台是C++编写,在对接过程中遇到了不少问题,所以有了这篇文章。

解决方案

直接上解决方案,以下是Node.js的加密代码:

var crypto = require('crypto');


function aes192Encrypt(data, key) {
   const cipher = crypto.createCipher('aes192', key);
   var crypted = cipher.update(data, 'utf8', 'hex');
   crypted += cipher.final('hex');
   return crypted;
}

对应的C++解密代码,使用OpenSSLCrypto++库:

unsigned char key[24] = { 0 };
unsigned char iv[16] = { 0 };
//(1)使用以下函数得到密钥
EVP_BytesToKey(EVP_aes_192_cbc(), EVP_md5(), NULL, (unsigned char*)pkey.c_str(), pkey.length(), 1, key, iv);


std::string outstr; //解密的结果
try {
    //(2)从hex字符串得到原始明文
  string decoded; 
  CryptoPP::StringSource ss(m_sign, true,
    new CryptoPP::HexDecoder(
    new CryptoPP::StringSink(decoded)
    ) // HexDecoder
  ); // StringSource


    //(3)使用aes-192-cbc解密
  CryptoPP::AES::Decryption aesDecryption(key, 24);
  CryptoPP::CBC_Mode_ExternalCipher::Decryption cbcDecryption(aesDecryption, iv);
  CryptoPP::StreamTransformationFilter stfDecryptor
        (cbcDecryption, 
         new CryptoPP::StringSink(outstr),
    CryptoPP::StreamTransformationFilter::PKCS_PADDING);
  stfDecryptor.Put(reinterpret_cast<const unsigned char*>(decoded.c_str()), decoded.size());


  stfDecryptor.MessageEnd();
}
catch(...)
{
  //解密失败处理
}

简单介绍上面的流程:

(1)使用EVP_BytesToKey得到真正的密钥和偏移量,Node.js的API封装得比较过分,内部调用了OpenSSL的EVP_BytesToKey函数计算真正的密钥和偏移量。如果没这一步,后面的步骤就通不了了。

(2)aes加密得到的密文通常包含很多不可显示的字符,通常将其base64hex处理,这里aes192Encrypt做了hex处理,所以解密时要反hex处理。

(3)典型的aes解密过程,注意aes-192的密钥是24位,且这里的补位模式是:PKCS_PADDING

分析过程

在解决这个问题的过程中,从网上找了不少资料,基本都是错的,最后耐下心一步一步分析得到了正解。

典型的网上错误的结论,一定不要相信:

  • crypto.createCipher('aes192', key);没有指定aes算法的模式,所以默认是ECB。

  • 原始的密文md5之后得到真正的密钥。

下面是我的分析过程:

(1)先从createCipher api找线索

想解密就要看怎么加密的,所以createDecipher是首个要查的对象,实际上Node.js的文档已经有说明:

The implementation of crypto.createDecipher() derives keys using the OpenSSL function EVP_BytesToKey with the digest algorithm set to MD5, one iteration, and no salt. The lack of salt allows dictionary attacks as the same password always creates the same key. The low iteration count and non-cryptographically secure hash algorithm allow passwords to be tested very rapidly.

上面的文字基本告知了怎么得到真正的密文.

如果往深了挖,可以看下node.js的源代码,加解密的底层是使用OpenSSL库实现的,主要逻辑都在node_crypto.cc文件里(https://github.com/nodejs/node/blob/master/src/node_crypto.cc).

(2) aes那么多模式,怎么知道是哪种?

EVP_BytesToKey函数第一个参数需要指定具体的加密算法,我们现在还无从得知模式信息。我们知道了node.js底层是使用OpenSSL库实现的,所以我们可以直接使用OpenSSL的代码得到aes192具体指代的模式:

const EVP_CIPHER* const cipher = EVP_get_cipherbyname("aes192");    
const int mode = EVP_CIPHER_mode(cipher);

通过上面的代码,能得到算法模式是CBC

(3)最后一个问题:补齐模式是哪个?

这个问题我没有在OpenSSL文档里得到答案,是从一个帖子里得到的信息,帖子地址:https://crypto.stackexchange.com/questions/10522/openssl-padding

帖子原文:

All the block ciphers normally use PKCS#5 padding also known as standard block padding

...

CBC and ECB modes require that the input consists in an integral number of blocks, so padding must be applied to ensure that.

大意是:正常情况下所有的块加密算法都是使用PKCS#5补位方式,包括CBCECB

总结

以上仅仅分析了aes-192算法,其他算法也类似。只要耐下心来寻找线索,一切问题都迎刃而解。

今天就写到这吧,希望能给你所有启示。喜欢我的文章,请关注我的公众号。

封面图片使用Thanga Vignesh P的作品。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值