【问题标题】:How to add salt to AES Encryption/Decryption?如何在 AES 加密/解密中添加盐?
【发布时间】:2021-09-27 04:31:28
【问题描述】:

我之前有一个关于在 Java 中实现加密和使用 CryptoJS 解密的问题已回答 here。在那个问题的 cmets 中,有人建议我实现某种密钥派生函数,而不是直接使用“秘密”。我的 CheckMarx 报告中也建议使用盐来使加密更加安全。这就是我从 Java 端加密的方式,连接 myKey 和 salt 的字节:

package aes;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Base64;
 
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class AesEncryption {

    private static SecretKeySpec secretKey;
    private static byte[] key;
 
    public static void setKey(String myKey) {
        MessageDigest sha = null;
        try {
            byte[] salt = generateSalt(16);
            key = Bytes.concat(myKey.getBytes(), salt);
            sha = MessageDigest.getInstance("SHA-1");
            key = sha.digest(key);
            key = Arrays.copyOf(key, 16); 
            secretKey = new SecretKeySpec(key, "AES");
        } 
        catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } 
    }

    public static String encrypt(String strToEncrypt, String secret) {
        try {
            setKey(secret);
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
            return Base64.getEncoder().encodeToString(cipher.doFinal(strToEncrypt.getBytes("UTF-8")));
        } 
        catch (Exception e) 
        {
            System.out.println("Error while encrypting: " + e.toString());
        }
        return null;
    }
    
    private static byte[] generateSalt(int length) {
        Random r = new SecureRandom();
        byte[] salt = new byte[length];
        r.nextBytes(salt);
        return salt;
    }
}

我现在遇到的麻烦是如何使用新添加的盐正确解密。这是从 JavaScript 端调用的解密函数:

aesDecrypt: (encryptedValue, aesSecret) => {
        var sha1Hash = CryptoJS.SHA1(aesSecret)
        var secretAesKey = CryptoJS.lib.WordArray.create(sha1Hash.words.slice(0, 16 / 4));
        var bytes = CryptoJS.AES.decrypt(encryptedValue, secretAesKey, {
          mode: CryptoJS.mode.ECB,
          padding: CryptoJS.pad.Pkcs7
        })
        var originalValue = bytes.toString(CryptoJS.enc.Utf8);
        return originalValue
}

既然添加了盐,我如何才能从字节数组中仅导出 secretAesKey?

【问题讨论】:

  • 你必须使用相同的盐
  • 在有效载荷中传递盐是常见的事情吗? @g00se
  • 为什么不使用专为此目的设计的可靠的密钥派生函数,如 PBKDF2。这比摘要更安全(即使使用盐)。此外,SHA1 和 ECB 也不是安全的选择。
  • 你可以在网上找到很多例子,例如Java/SCEE。该解决方案除了使用 PBKDF2 之外,还使用了一种与随机 IV/nonce 相结合的安全模式。
  • CryptoJS 不支持 GCM 模式。但是该链接提供了跨平台实现,也适用于 JavaScript 和各种 JavaScript 库(SJCL、WebCrypto API、NodeJS)。 here。如果你想使用 CryptoJS,你必须将模式更改为例如加拿大广播公司。然后必须由您自己实施身份验证(例如使用 MAC)。

标签: javascript java encryption aes


【解决方案1】:

您的解决方案中有多个主要问题:

        byte[] salt = generateSalt(16);
        key = Bytes.concat(myKey.getBytes(), salt);
      ... 
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");

既然添加了盐,我如何才能从字节数组中仅导出 secretAesKey?

您的代码中的重大错误是您正在创建一个随机密钥(连接或随机盐和密码)。您无法恢复原始值。加密时盐是随机的,但解密时需要知道。

我想说明的第二点是使用 ECB 模式。尝试阅读和理解不同的mode of operations。 EBC 模式实际上只使用了一个键(无盐/IV),并且这种模式有几个继承的弱点,我真的建议使用其他模式(CBC、GCM、..)。

加密有两个随机输入:

  • salt 获取密码,尝试搜索 KDF(PBKDF2、BCrypt、SCrypt、Argon2),您可以在其中从密码生成加密密钥。使用简单的哈希可能不足以从密码创建加密密钥。
  • IV(初始化向量)确保明文和密钥的唯一且不可预测的组合。如果您有一个随机密码盐生成随机加密密钥,那么 IV 可以是静态的。

这些随机值(IV 或 salt)是解密所需的加密参数,因此这些值通常是沿密文传递的,不需要保密。

如果您想返回单个编码字符串,那么您可以尝试创建类似salt|ciphertext|mac 的内容。密码对称加密见some examples

我对JS不是很精通,所以我把细节留给你,但你需要在两种语言中使用相同的参数和值

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-02-24
    • 1970-01-01
    • 2016-04-09
    • 2011-11-10
    • 1970-01-01
    • 2016-09-22
    • 2016-07-08
    相关资源
    最近更新 更多