当前位置:C++技术网 > 精选软件 > AES加密解密前后台对接的问题研究总结

AES加密解密前后台对接的问题研究总结

更新时间:2016-06-30 12:40:03浏览次数:1+次

    AES加密是一种对称加密方法,就和我们压缩加密时填写一个密码加密,以后可以用这个密码解密是一个道理。对于AES的算法内部机制我就没有仔细研究,也没有一个由浅入深的文章解释,看百度查出来的文章,只有一个冲动,那就是卸载百度。大量重复的文章到处转载,查来查去还是那几个文章,但是却不顶用,也不知道文章讲的啥。后来用谷歌查出来的结果还是很大的不一样,其实都没有什么效果。
    不知道是不是AES算法真的很复杂。我没有专门研究加密解密的东西,所以,这里也就是就我开发的过程做一个细致的总结,供大家参考。
    到目前为止,还是有一个坑没有跳出去,不过因为这个坑不大影响实际的使用,所以暂时忘却,毕竟,不专门从事这个领域,而且业务能够满足,所以没有需要就能绕过去就绕过去。其实我心里是想把坑填平的。
    现在的结果是,我用正确的密码算出解密的秘钥,然后去解密密文,可以解密成功,而且发给后台后,验证也是通过的。好了,密码正确的时候,没有问题了。这是文章《高手眼中并没有代码,选对了方向方法会事半功倍》总结的方法思路才让我找到了这个结果。兴奋之余,我用错误的密码输入,然后解密,结果崩溃了。提示:
填充无效,无法被移除。
异常详细信息: System.Security.Cryptography.CryptographicException: 填充无效,无法被移除。
    我这个是asp.net的程序,后台是C#代码。而且,只要密码正确,就能够正确的解密,密码错误就会提示这个崩溃信息。这也让我想起之前为什么总是崩溃,而我一直去找崩溃的原因。此前,我大部分是用错误的密码去试的。因为我从来没有想过,会因为密码不对而导致解密崩溃,我只会以为,错误的密码只是解密得到的明文不对嘛。完全是在预料之外,所以总是不知如何是好。
    还好通过前面的总结,理清了一套研究思路,然后一步步的验证,通过正确的密码去解密,然后成功了。之前的方法其实没有什么大问题,只是遇见这样的坑,我也是醉了。
    开始我使用直接与后台对接的黑盒测试,可能造成错误的因素太多了,加大了困难。我先将我做的流程解释一下。
    后台会产生一个随机字符串,32个字符。这个随机字符串会被加密,然后传个前端。加密使用的秘钥是用用户的密码先使用SHA256加密,得到了64位的SHA256加密密文,因为AES的KEY需要是32位的,所以截取后32位的SHA256加密密文,这样就得到了秘钥。
    因为AES是对称加密,用同一个秘钥既可以加密,也可以解密。后台用用户注册后的密码来创建秘钥,用户登录时会输入一个密码,前端就用这个密码去产生秘钥。这样,两端的秘钥就是一样的了。所以双方一个加密一个解密就可以正确的识别密文了。
    后台用这个秘钥加密产生的32位的随机字符串,就得到一个随机字符串的AES加密密文,这个密文就传给了前端。前端用产生的秘钥来解密AES加密密文,得到的明文就是一个随机字符串。然后前端将解密后的结果发给后台验证,如果和后台的随机字符串一样,说明就正确了。
    后台是没有问题的,我们现在做前端的程序,与后台对接。所以我们只需要解密就行了。在正确密码的情况,我们产生的秘钥是正确的,能够解密。但是如果输入的密码是错误的,产生的秘钥不是不能解密,而是一解密就崩溃了。对于应用来说,只要正确的密码解密都是正常的,不正确的密码崩溃异常,我捕获异常,就可以了。反正能够正确登录,至于错误,只要我能够判断就行了。我不用去处理为什么错误,错误信息如何,都没有关系,错了就错了,错了就不让你登录就行了。是的,我就这样暂时先解决登录问题,至于为什么密码不正确,导致程序崩溃,暂时不研究了。
    前面提到的文章给了正确的研究思路,让要考虑的因素从前后台不一致变成前端自己加解密的一致状态,这样我只需要得到后台加解密的数据,然后拿到前台来试验,从而来调整参数,如果加解密的结果和后台的数据一致了,说明和后台的做法就一致了。而不是在后台参数不清楚,而且是跨语言的实现,诸多不确定因素的情况下,直接对接,难度可想而知。除非你能够非常熟悉两边的语言,才好直接对接。刚好,我前端不太熟,后端没学过,大致意思凭经验靠猜。所以更是加大难度了。转入前端一致的环境,有一个标准代码,然后调整参数,就容易的多了。就是这样的一个改进,促进我找到了正确的结果。
    我先写好AES的加密和解密的代码,随便使用一个明文,秘钥是自定义的,加密解密,正确的。然后再进一步,把秘钥换成程序中创建的,也就是用用户输入的密码来创建的秘钥,来加解密,这样得到的结果也是没有问题的。因为此时的情况是,我始终都是用同一个秘钥加解密,所以等于说,密码始终都是正确的。暂时没有测试密码不正确的解密。
    这样一来,就说明本地代码暂时可用。然后就将解密函数拿来解密后台传过来的密文,秘钥则是根据登录输入的密码产生。我还是先用正确的密码产生了秘钥。通过调试,慢慢修改参数,然后发现改了一个参数之后,得到的明文和后台解密得到的明文一致了。
    到这里,解密就成功了。这里使用的方法就是本地代码先测试正常,再一步步的切入正式的使用环境,而且先保证密码正确的情况的解密。此时解密成功才是第一步。
    然后将解密得到的字符串,也就是32位随机字符串,传给后台,还是错误。不过此时,我很确定,解密是正确的。再也不会和之前一样,又陷入解密问题上。此时我知道,这是传输到后台的参数处理的问题。然后我仔细看了后台的python代码,虽然代码很多语法看不懂,但是基本的单词和逻辑流程,还是可以看懂的。但是要我写Python代码,无从下手,最多就是先模仿着写一下。我也是这样得到的后台正确的数据,拿给前端测试。然后我发现后台要先将获取的参数进行base64解密,而我们解密后,并没有用bsae64就传过去了。不过没有相关文档说明,所以一开始没有这么做。这也给开始的直接对接后台造成了困难。
    在给解密后的明文进行base64编码时,要面对一个字符编码问题,是用ASCII还是UTF8呢。不确定,可能是UTF8。不过我先用其他网站提供的在线解密,来确定一下。不过Python后台使用的是utf8,加之后台提示是UTF8的错误提示,所以可以确定是用UTF8的字符编码,这样先将解密后的明文用UTF8编码,再用base64编码,再传给后台。后台再通过base64解密,就不会提示UTF8编码之类的错误了。如此一推测,然后写好了对应的代码。鉴于前面的+号问题,为了防止一些字符被过滤,所以再加上url编码,后端服务器会自动url解码。python程序处理的参数是已经自动url解码过的。然后测试了一下,成功了。
    可以看到,有了明确的科学的思路,过程一步步的进行着,后面的操作都是基于前面可靠的结果,所以不会再倒回去。这样只会一步步的朝成功逼近,而不是反复折腾。这也是选择正确的研究方法,比埋头苦干强的原因了。我半个小时解决了这个问题,此前花了两天也没有得到一个正确结果。
    通过测试才最终确定,前端使用AES的参数为:加密块大小(BlockSize)为128,加密模式为ECB,填充模式为PKCS7。参数错误,得到的解密结果不一样,长度也不一样。加密解密只有一致才行。
    因为我主要是对前后台对接的问题研究做一个总结,具体的AES相关流程就没必要画图了,毕竟这个过程不一定通用。不过为了能够让你能够找到一点感觉,我还是贴出C#的加解密两个函数的代码(第一个是加密,第二个是解密):
public static string AESEncrypt(string toEncrypt,string strKey32)
{
    byte[] keyArray = UTF8Encoding.UTF8.GetBytes(strKey32);
    byte[] toEncryptArray = UTF8Encoding.UTF8.GetBytes(toEncrypt);

    RijndaelManaged rDel = new RijndaelManaged();
    rDel.BlockSize = 128;//128,192,256
    rDel.Key = keyArray;
    rDel.Mode = CipherMode.ECB;
    rDel.Padding = PaddingMode.PKCS7;

    ICryptoTransform cTransform = rDel.CreateEncryptor();
    byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
    return Convert.ToBase64String(resultArray, 0, resultArray.Length);
}
public static string AESDecrypt(string toDecrypt, string strKey32)
{
    byte[] keyArray = UTF8Encoding.UTF8.GetBytes(strKey32);
    byte[] toEncryptArray = Convert.FromBase64String(toDecrypt);

    RijndaelManaged rDel = new RijndaelManaged();
    rDel.BlockSize = 128;
    rDel.Key = keyArray;
    rDel.Mode = CipherMode.ECB;
    rDel.Padding = PaddingMode.PKCS7;

    ICryptoTransform cTransform = rDel.CreateDecryptor();
    byte[] resultArray;
    string strRet = "";
    resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
    strRet = UTF8Encoding.UTF8.GetString(resultArray);
    try
    {
        resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
        strRet = UTF8Encoding.UTF8.GetString(resultArray);
    }
    catch (System.Exception ex)
    {
    }

    return strRet;
}

    在解密前,先将秘钥解码为UTF8编码,并转为字节数组。对密文先base64解码。返回的明文也要先用UTF8编码字符串返回。方便外面程序处理。此代码不一定完全正确,所以仅供参考。毕竟秘钥不正确的时候,解密会出现异常。这个还不太清楚原因。需要深入学习AES加密解密。
    那么在本地测试后台的加密解密数据的代码如下:
//random_str:InC70Axu4a3wgRPjGsVYvMl5qTQyXBWK encrypt:2OUEnmWwq6kt4nELP/cI2mVgb7IIHumV3BuUIDwzFlh/1JS/Yq7h8Mwf8PLnWVyO
//random_str:9qa52mDnKTNgEShkFPAbGYL8XjCrtudo encrypt:l7duYnrLHjCzDhimTZOcmJH8xLDKq60Kb6+Zl8jT9a5/1JS/Yq7h8Mwf8PLnWVyO
//random_str:hBtG8H2g5M4UKE6xOz7qRQuLjyfADrIw encrypt:7Idu2x3oYdLDS06/BH0yW8LERLdw8Q4YVag2KNjDPrR/1JS/Yq7h8Mwf8PLnWVyO
//random_str:02aFH5OVhGS3tnepKc61ToYPsbmfzlUZ encrypt:dNbDQfh+vdd+MzAhiG3NCzuzwGKBQxt8+ZqKGRR1bEZ/1JS/Yq7h8Mwf8PLnWVyO
//random_str:W83QUhyk9qYoFb4512X7JxtfSZaCvdRG encrypt:eVGsfWVIyTsmfkbcuiRF9sxg8390boS7D+oF/OeNw5B/1JS/Yq7h8Mwf8PLnWVyO
//random_str:4d1MoRg3bQvJp8hnBTFAz9flkZCY5IDq encrypt:g/IgWGbXx/z4DwjZEaMiOppRxU/8cxmwPQ4dWpxEe3x/1JS/Yq7h8Mwf8PLnWVyO
//random_str:URQgV0jNPZhSov38ALJd7iHpYckewE1z encrypt:3p0RO68/EQfueKBotqD4yr9R2ozfosZ/Y6j6QyXZ5+d/1JS/Yq7h8Mwf8PLnWVyO

string test = "InC70Axu4a3wgRPjGsVYvMl5qTQyXBWK";
string miwen = AESEncrypt(test, aesKey);//加密
string mingwen = AESDecrypt(EncryptString, aesKey);//解密

    有很多样本数据可供测试,这里给出来只是供大家理解的。实际去写代码实现一遍,还有许多细节要处理。