【问题标题】:AES-256 produces different ciphertexts in iOS and Node.js with same passwordAES-256 在 iOS 和 Node.js 中使用相同的密码生成不同的密文
【发布时间】:2016-01-10 20:22:23
【问题描述】:

所以我有我的 iOS 代码:

#import <CommonCrypto/CommonCrypto.h>
NSString* password = @"1234567890123456";
NSString* salt = @"gettingsaltyfoo!";
-(NSString *)decrypt:(NSString*)encrypted64{

    NSMutableData* hash = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
    NSMutableData* key = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
    CC_SHA256(salt.UTF8String, (CC_LONG)strlen(salt.UTF8String), hash.mutableBytes);
    CCKeyDerivationPBKDF(kCCPBKDF2, password.UTF8String, strlen(password.UTF8String), hash.bytes, hash.length, kCCPRFHmacAlgSHA1, 1000, key.mutableBytes, key.length);
    NSLog(@"Hash : %@",[hash base64EncodedStringWithOptions:0]);
    NSLog(@"Key : %@",[key base64EncodedStringWithOptions:0]);


    NSData* encryptedWithout64 = [[NSData alloc] initWithBase64EncodedString:encrypted64 options:0];
    NSMutableData* decrypted = [NSMutableData dataWithLength:encryptedWithout64.length + kCCBlockSizeAES128];
    size_t bytesDecrypted = 0;
    CCCrypt(kCCDecrypt,
            kCCAlgorithmAES128,
            kCCOptionPKCS7Padding,
            key.bytes,
            key.length,
            NULL,
            encryptedWithout64.bytes, encryptedWithout64.length,
            decrypted.mutableBytes, decrypted.length, &bytesDecrypted);
    NSData* outputMessage = [NSMutableData dataWithBytes:decrypted.mutableBytes length:bytesDecrypted];
    NSString* outputString = [[NSString alloc] initWithData:outputMessage encoding:NSUTF8StringEncoding];

    NSLog(@"Decrypted : %@",outputString);


    return outputString;
}
-(NSString *)encrypt:(NSString *)toEncrypt{
    NSMutableData* hash = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
    NSMutableData* key = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
    CC_SHA256(salt.UTF8String, (CC_LONG)strlen(salt.UTF8String), hash.mutableBytes);
    CCKeyDerivationPBKDF(kCCPBKDF2, password.UTF8String, strlen(password.UTF8String), hash.bytes, hash.length, kCCPRFHmacAlgSHA1, 1000, key.mutableBytes, key.length);

    NSData* message = [toEncrypt dataUsingEncoding:NSUTF8StringEncoding];
    NSMutableData* encrypted = [NSMutableData dataWithLength:message.length + kCCBlockSizeAES128];
    size_t bytesEncrypted = 0;
    CCCrypt(kCCEncrypt,
            kCCAlgorithmAES128,
            kCCOptionPKCS7Padding,
            key.bytes,
            key.length,
            NULL,
            message.bytes, message.length,
            encrypted.mutableBytes, encrypted.length, &bytesEncrypted);
    NSString* encrypted64 = [[NSMutableData dataWithBytes:encrypted.mutableBytes length:bytesEncrypted] base64EncodedStringWithOptions:0];
    NSLog(@"Encrypted : %@",encrypted64);
    return encrypted64;
}

我有我的 node.js 代码:

var CryptoJS = require("crypto-js");
var crypto = require('crypto');
var password = "1234567890123456";
var salt = "gettingsaltyfoo!";
var hash = CryptoJS.SHA256(salt);
var key = CryptoJS.PBKDF2(password, hash, { keySize: 256/32, iterations: 1000 });

var algorithm = 'aes128';

function encrypt(text){
  var cipher = crypto.createCipher(algorithm,key.toString(CryptoJS.enc.Base64));
  var crypted = cipher.update(text,'utf8','hex');
  crypted += cipher.final('hex');
  console.log(crypted);
  console.log(hash.toString(CryptoJS.enc.Base64));
  console.log(key.toString(CryptoJS.enc.Base64));
  return crypted;
}

function decrypt(text){
  var decipher = crypto.createDecipher(algorithm,key.toString(CryptoJS.enc.Base64));
  var dec = decipher.update(text,'hex','utf8');
  dec += decipher.final('utf8');
  console.log(dec);
  return dec;
}

问题:不幸的是,虽然我有相同的哈希、密钥和最终解密的值(意味着它们可以独立工作),但我得到的加密值不同。因此,在一个代码中,如果我采用加密值并尝试在另一个代码中对其进行解密,则会出现错误。当我从 iOS 转到节点时,我收到此错误:

错误:数字信封例程:EVP_DecryptFinal_ex:错误的最终块长度代码:解密('vfOzya0yV9G5hLHeSh3R1g==');

我还得到了字符串“Hello World”的这些不同的加密值:

IOS: vfOzya0yV9G5hLHeSh3R1g==
NODE: 13b51a6785f47d8601c3a612d41b9a8b

我该如何解决这个问题,以便我可以互操作我的 iOS 和 Node.js,以及未来的 Android。我知道我的散列算法适用于生成 SHA256 和 PBDKF2,因为我得到了相同的散列和密钥。这意味着在加密我的密码时,我的实现对于 AES128 是错误的。很可能是我的 iOS 代码。请让我知道我的错误在哪里。

【问题讨论】:

  • 抱歉,有点新,所以我只是假设编辑我的问题的人知道他或她在做什么。
  • 您最好将附加信息添加到 7 小时前询问的 question,因为代码是相同的。并且代码只是这个SO Answer中代码的一个副本,所有的属性都被删除了。
  • 对于初学者来说:13b51a6785f47d8601c3a612d41b9a8b 是十六进制编码,vfOzya0yV9G5hLHeSh3R1g== 是 Base64 编码,解码为十六进制是:bdf3b3c9ad3257d1b984b1de4a1dd1d6 - 仍然不相等。您需要在每个步骤中对每个执行十六进制转储以确定差异发生的位置。当您使用来自其他来源的代码时,它会成为您的代码,您有责任使其工作。可以潜入和调试。
  • @zaph 我应该包括归属地。我忽略了这一点,因为我的代码略有不同,因为它使用加密模块和 CryptoJS 的方式略有不同。无论如何,我会确保下次这样做,所以感谢您指出这一点。但至于你的第一条评论,我相信我有理由提出一个新问题,因为第一个问题是关于我的安全影响。这个问题是关于缺乏互操作性的。

标签: javascript objective-c node.js encryption aes


【解决方案1】:

您不需要使用 CryptoJS,因为 node.js 的 crypto module 提供了您所需的一切。 CryptoJS 的二进制表示与 node.js 的原生 Buffer 不同,所以两者结合使用会出现问题。

问题:

  • 您正在使用crypto.createCipher(),它将以 OpenSSL 兼容格式自行从密码中派生密钥。你想使用crypto.createCipheriv()
  • 您没有在 Objective-C 中将 IV 传递给默认为零填充的 IV。您需要在 node.js 中通过初始化一个零填充的 Buffer 来执行相同的操作。
  • 您在 node.js 中以 Base64 编码形式提供密钥,但您必须提供字节(缓冲区)。
  • 由于密钥大小为 256 位,您实际上使用的是 AES-256 而不是 AES-128。尽管指定了 128 位,CommonCrypto 代码似乎会自动更改为 256 位,但 node.js 要求您明确指定 256 位。此外,“aes128”或“aes256”在 node.js 中将默认为 ECB 模式,但 CommonCrypto 默认为 CBC 模式,因此您需要明确指定。

完整的工作代码:

var crypto = require('crypto');
var password = "1234567890123456";
var salt = "gettingsaltyfoo!";

var sha256 = crypto.createHash("sha256");
sha256.update(salt);
var hash = sha256.digest();

var key = crypto.pbkdf2Sync(password, hash, 1000, 32, "sha1");

var iv = new Buffer(16);
iv.fill(0);

var algorithm = 'aes-256-cbc';

function encrypt(text){
  var cipher = crypto.createCipheriv(algorithm, key, iv);
  var crypted = cipher.update(text,'utf8','base64');
  crypted += cipher.final('base64');
  return crypted;
}

function decrypt(text){
  var decipher = crypto.createDecipheriv(algorithm, key, iv);
  var dec = decipher.update(text,'base64','utf8');
  dec += decipher.final('utf8');
  return dec;
}

console.log(encrypt("Hello World"));

输出:

vfOzya0yV9G5hLHeSh3R1g==

其他注意事项:

  • 您需要为您执行的每次加密生成一个随机 IV。如果您不这样做,那么如果您每次都使用相同的密钥,攻击者可能会看到您多次加密了同一条消息而没有真正解密它。由于您从密码中派生密钥,因此您可以通过生成随机盐并从 PBKDF2 派生 384 位(48 字节)来更好地执行此操作。将前 32 个字节用作密钥,其余字节用作 IV。

  • 您需要对密文进行身份验证。如果您不这样做,那么攻击者可能会在您的系统上安装填充 oracle 攻击。您可以通过在密文上运行 HMAC 并将生成的标签连同它一起发送来轻松地做到这一点。然后,您可以在解密前通过在收到的密文上再次运行 HMAC 来验证标签,以检查是否存在操纵。
    或者你可以使用像 GCM 这样的认证模式。

【讨论】:

  • 为什么要执行 pbkdf2 salt 的 sha256 哈希?请注意,代码只是 SO 答案的副本,而不是 OP 的。
  • 谢谢,我不记得那个问题了。反正解决后第一句话忘记去掉了。
  • 两个简单的问题,我对 node.js 有点陌生,所以我将不得不看看如何使用 HMAC。但与此同时,如果它很快,我会很感激一个关于如何使用它的快速代码示例。我还有一个关于如何将 iOS 加密数据传输到后端的问题。所以我在后端考虑,当消息第一次加密时,我已经存储了密钥,并将这两部分与我前端的新加密消息和新密钥进行比较。这是正确的吗,因为如果不将密钥从前端转移到后端,我无论如何都看不到这样做。
  • 对创建 salt/iv 的一些帮助也会有所帮助!
  • @user2977578 在开始编程之前,您应该考虑过密钥管理。您需要使用非对称加密,例如 RSA,但您也可以只使用内置所有内容的 TLS。
猜你喜欢
  • 2012-06-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-04
  • 2017-06-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多