使用OpenSSL进行加密,与Java相同的方式

6

我需要使用bash脚本以与javax.crypto.Cipher相同的方式加密字符串。在Java中,我使用AES-256和密钥“0123456789”。但是,当我使用openssl时,我必须将“0123456789”转换为十六进制,但结果与Java不同。

echo "lun01" | openssl aes-256-cbc -e -a -K 7573746f726530313233343536373839 -iv 7573746f726530313233343536373839

dpMyN7L5HI8VZEs1biQJ7g==

Java:

public class CryptUtil {
    public static final String DEFAULT_KEY = "0123456789";

    private static CryptUtil instance;

    private String chiperKey;

    private CryptUtil(String chiperKey) {
        this.chiperKey = chiperKey;
    }

    public static CryptUtil getInstance() {
        if (null == instance) {
            instance = new CryptUtil(DEFAULT_KEY);
        }

        return instance;
    }

    public static CryptUtil getInstance(String cipherkey) {
        instance = new CryptUtil(cipherkey);
        return instance;
    }

    public String aesEncrypt(String plainText) {
            byte[] keyBytes = Arrays.copyOf(this.chiperKey.getBytes("ASCII"), 16);

            SecretKey key = new SecretKeySpec(keyBytes, "AES");
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(Cipher.ENCRYPT_MODE, key);

            byte[] cleartext = plainText.getBytes("UTF-8");
            byte[] ciphertextBytes = cipher.doFinal(cleartext);
            final char[] encodeHex = Hex.encodeHex(ciphertextBytes);

            return new String(encodeHex);

        return null;
    }

    public static void main(String[] args) {

        CryptUtil cryptUtil = CryptUtil.getInstance();
        System.out.println(cryptUtil.aesEncrypt("lun01"));
    }
}

d230b216e9d65964abd4092f5c455a21


是的,我明白。但它仍然没有正确转换。我编辑了问题... - Felipe Gutierrez
1
你的十六进制值 7573746f726530313233343536373839 是字符 ustore0123456789 而不是 0123456789 加上空字符,正如 @Artjom 所说的那样,你的 Java 使用 AES-128 ECB 模式(和 OpenSSL 一样使用 PKCS#5 填充)。此外,echo 命令(在 bash 中是内置命令)会向数据添加一个换行符,但是你的 Java 没有这样做,而 openssl enc -a 将结果编码为 base64 而不是十六进制。修复这些问题:echo -n lun01 |openssl aes-128-ecb -K 30313233343536373839000000000000 |od -tx1 给出了与你的 Java 结果相匹配的 0000000 d2 30 b2 16 e9 d6 59 64 ab d4 09 2f 5c 45 5a 21 - dave_thompson_085
1个回答

6

如果无数的在线十六进制转换器对你无效,那么你可以简单地将你在Java中使用的密钥以十六进制形式打印出来。这里是一个关于这个“壮举”的热门SO问题。

在你完成这一步之后,你会发现它仍然不起作用,因为你正在使用不同的算法。

当你使用Cipher.getInstance("AES");时,它很可能默认为"AES/ECB/PKCS5Padding",这与"aes-256-cbc"不同,因为ECB和CBC是两种完全不同的操作模式。为了避免这种歧义,始终完整地指定你的密码,例如:Cipher.getInstance("AES/CBC/PKCS5Padding");

然后,在Java中生成的密钥只有16字节长,所以在OpenSSL中匹配的密码是"aes-128-ecb"。

正如dave_thompson_085在评论中所说:

  • echo 会在输出后自动添加一个换行符,而你的Java代码不会添加。你需要像这样创建纯文本:echo -n "lun01"。如果你使用Windows系统,请参考this

  • 你的Java代码以十六进制输出结果,所以你需要在OpenSSL中也进行相同的操作。你需要在OpenSSL命令中去掉-a选项,以防止Base64编码,并且可以利用其他命令行工具(如Linux上的od)将二进制输出数据转换为十六进制,使用od -tx1即可。

  • 完整命令:

      echo -n lun01 |openssl aes-128-ecb -K 30313233343536373839000000000000 |od -tx1
    

安全建议

不要使用ECB模式!它不是语义上安全的。您需要至少使用带有随机IV的CBC模式(检查它是否随机而不仅仅是零字节)。

更好的方法是通过添加HMAC标签来进行身份验证,例如使用加密-然后-MAC方法或简单地使用像GCM这样的经过身份验证的模式。

附加信息

如果您使用的不是ECB,则不能在两个版本中加密相同的内容并期望出现相同的密文。由于它是随机的,因此您需要在一个版本中加密并在另一个版本中解密以确保兼容性。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接