【问题标题】:AES with Rijndael's key scheduleAES 与 Rijndael 的密钥时间表
【发布时间】:2015-05-07 01:59:57
【问题描述】:

我正在使用 java security 和 openssl 来使用 aes-ecb-256 加密特定字符串,但结果并不相同。 有没有实现Rijndael's key schedule 的java 库?

C 代码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <openssl/aes.h>

using namespace std;

int main(int argc, char** argv) {
    AES_KEY aes;
    char* key = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
    unsigned char* input_string;
    unsigned char* encrypt_string;
    unsigned char* decrypt_string;
    unsigned int len;        // encrypt length (in multiple of AES_BLOCK_SIZE)
    unsigned int i;

    // check usage
    if (argc != 2) {
        fprintf(stderr, "%s <plain text>\n", argv[0]);
        exit(-1);
    }

    // set the encryption length
    len = 0;
    if ((strlen(argv[1]) + 1) % AES_BLOCK_SIZE == 0) {
        len = strlen(argv[1]) + 1;
    } else {
        len = ((strlen(argv[1]) + 1) / AES_BLOCK_SIZE + 1) * AES_BLOCK_SIZE;
    }

    // set the input string
    input_string = (unsigned char*)calloc(len, sizeof(unsigned char));
    if (input_string == NULL) {
        fprintf(stderr, "Unable to allocate memory for input_string\n");
        exit(-1);
    }
    strncpy((char*)input_string, argv[1], strlen(argv[1]));

    if (AES_set_encrypt_key((unsigned char*)key, 256, &aes) < 0) {
        fprintf(stderr, "Unable to set encryption key in AES\n");
        exit(-1);
    }

    // alloc encrypt_string
    encrypt_string = (unsigned char*)calloc(len, sizeof(unsigned char));
    if (encrypt_string == NULL) {
        fprintf(stderr, "Unable to allocate memory for encrypt_string\n");
        exit(-1);
    }

    // encrypt
    AES_ecb_encrypt(input_string, encrypt_string, &aes, AES_ENCRYPT);


    // print
    printf("input_string = %s\n", input_string);
    printf("encrypted string = ");
    for (i=0; i<len; ++i) {
        printf("%02X ", encrypt_string[i]);
    }
    printf("\n");
    //printf("decrypted string = %s\n", decrypt_string);

    return 0;
}

Java 代码:

public class testMain {
    private static byte[] key = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".getBytes();
    private static byte[] plaintext = "fjaiejrfoi".getBytes();
    private static Cipher cipher;
    private static byte[] result;

    public static void main(String[] args) {
        try {
            cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        }
        SecretKeySpec secretKey = null;
        try {
            secretKey = makeKey();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        try {
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        }
        try {
            result = cipher.doFinal(plaintext);
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        }
        StringBuffer sb = new StringBuffer();
        for(byte b : result) {
            sb.append(Integer.toHexString((int)(b&0xff)));
        }
        System.out.println("result is " + sb.toString());

    }

    private static SecretKeySpec makeKey() throws NoSuchAlgorithmException{
        KeyGenerator kgen = KeyGenerator.getInstance("AES");
        kgen.init(256, new SecureRandom(key));
        SecretKey secretKey = kgen.generateKey();
        SecretKeySpec keySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
        return keySpec;
    }
}

纯文本是 fjaiejrfoi。两段代码的结果不同。

【问题讨论】:

  • 您到底希望实现什么?使用 AES 加密纯文本?
  • Java 和 openssl 都支持 ecb 模式下的 256 位密钥的 aes。显示您的代码。
  • 我已经更新了代码。
  • AES 是具有 128 位块的 Rijndael(Rijndael 还提供 192 位和 256 位块,而 AES 不提供)。 AES 使用 Rijndael 的密钥调度。
  • “是否有任何用于实现 Rijndael 密钥调度的 java 库?” - 这是在 Stack Overflow 上提出的错误问题。请参阅帮助中心的 What topics can I ask about here?What types of questions should I avoid asking?

标签: java openssl aes


【解决方案1】:

填充不同。您的 C++(不是 C)代码隐式零填充(来自 calloc)。您的 Java 代码指示 JCE 使用 PKCS#5 填充,它永远不会为零。 (有点迂腐,对于像 AES 这样的 16 字节块,它实际上是 PKCS#7 而不是 PKCS#5,但几乎每个人都只是称它为 PKCS#5。)

密钥不同。您的 C++ 代码使用 16 个 ASCII 'a'(十六进制 0x61)作为密钥。您的 Java 代码使用了一个随机生成的密钥,该密钥每次都不同(概率极高)。

ECB 和确定性加密无论如何都是不好的。 除非您的数据是高度熵的(理想情况下是完全随机的,通常也很小)ECB 是个坏主意。有关基本解释,请参阅http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation 和(不)著名的欧洲央行企鹅。 或者在 crypto.SE 或 security.SE 中搜索 ECB,你会发现几十个答案一遍又一遍地解释为什么 ECB 不好。更一般地说,任何非随机加密都是不好的做法,因为它允许对手区分两个密文是否是相同的明文,即使不知道明文是什么。

而且您的错误处理能力很差。您的 C++ 代码打印错误并退出,但以 -1 作为退出状态。这会在不同的系统上给出不同的结果,而在我使用过的系统上永远不会 -1。 0 到略小于 127(比如 120)可移植到所有 Unices 和 Windows,但标准 C 保证只有 0 和来自 EXIT_SUCCESSEXIT_FAILURE 的两个值。在丢失或无效数据的情况下继续执行,这根本无法正常工作。它也应该退出——或者对于一次性代码,你可以省略异常处理程序; JVM 对未处理异常的默认设置是打印堆栈跟踪并退出。

旁白: 连接 Integer.toHexString(val) 会产生无法使用的结果。字节 12 03 将产生 123,字节 01 23 也将产生;读取 123 时,您无法分辨哪个是实际数据。添加分隔符,或者更好(更一致)使用String.format ("%02x", val),就像你对C所做的那样。而且你不需要演员;在 Java 表达式中,小于 int 的整数类型被提升。 (在 C 和 C++ 中也是如此,但它们稍微复杂一些,因为它们必须同时处理所有等级的有符号和无符号整数类型。Java 整数都是有符号的,除了 char。)

在 Java 加密中,当您拥有 Key 对象时,您不需要将其转换为 KeySpec 以将其用于加密。事实上,通常情况是相反的。对于 RSA 等更结构化的算法,您可以使用密钥,如果您有 KeySpec,则必须进行非平凡的转换(通常由工厂)才能获得密钥。但由于秘密/对称密钥只是位,因此 SecretKeySpec 既是 KeySpec 又是 Key。

编辑: JCE 不会为您做零填充,因为它对一般数据不安全;您不能始终如一地解密您加密的相同数据,Java 设计者和大多数用户认为这是一个缺陷。因为你不关心你的数据,你可以用零填充——在Java中使用标准库类java.util.Arrays很容易做到这一点

 // given byte[] data and assuming min1:maxblock padding like your C code
 final int BLOCKSIZE = 16; // for AES, can be inlined
 int padded_len = (data.length + BLOCKSIZE)/BLOCKSIZE*BLOCKSIZE;
 byte[] padded = Arrays.copyOf (data, padded_len); // adds zeros

然后使用没有填充的 JCE 密码,这里是 "AES/ECB/NoPadding"。 但是解密方面是一个问题:编写一个显式循环来删除尾随的零字节需要几行代码——但其中一些可能是实际数据而不是填充,在这种情况下你自己搞砸了。

更好的解决方案是在 OpenSSL 中使用 PKCS#5(7) 填充。低级例程不这样做,但高级 EVP 模块会自动这样做。只需将EVP_{Encrypt,Decrypt,Cipher}{Init,Update,Final} 与从EVP_aes_256_ecb() 获得的密码对象一起使用——或者最好使用上述更好的模式。

您在 OpenSSL 中使用的密钥是“a”的 32 个字节(每个 8 位)——除非您使用 EBCDIC 运行机器,但如果是这样,您会知道它。 (确切地说,OpenSSL 使用 C 和 C++ 类型 unsigned char可以超过 8 位,但 OpenSSL 仅支持正好是 8 位的平台/目标。)Java 加密使用数组byte 是 8 位(但有符号)。因此,您需要一个 32 个字节的数组,每个字节包含“a”。你可以从字符串中得到这个

 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".getBytes() // safe for ASCII only, see below

或直接但更笨拙的是

 byte[] key = { 'a', 'a', 'a', .... total 32 times ... };
 // or without the variable as new byte[] { ... same ... };

或许多其他方式,例如

 byte[] key = new byte[32];
 Arrays.fill (key, (byte)'a');

注意String.getBytes(/*no-args*/) 如果密钥中有或可能(曾经)是非 ASCII 字符,则密钥是不安全的。 no-args 重载使用可能取决于平台和其他环境因素(例如用户和语言环境)的默认编码;这意味着它在解密时可能会产生与加密时不同的密钥字节,并且使用不同的(因此错误的)密钥字节会失败。如果您需要在 ASCII 之外使用 characters(如重音字母),则必须使用指定“Charset”(Java 编码)的(两个)重载之一,如果这是多字节编码(UTF8 是主要示例)您需要确保该值是正确的字节长度,而不是(必须)以字符为单位的长度。更好的是首先将键视为字节序列,而不是字符。

【讨论】:

  • 谢谢,看来我不能在java中使用零填充,key("aaa..") 不能使用key schedule扩展到256位,所以我怎样才能得到相同的通过 openssl 和 java 的结果?
猜你喜欢
  • 2016-05-26
  • 2012-07-20
  • 1970-01-01
  • 1970-01-01
  • 2023-03-23
  • 2012-05-02
  • 1970-01-01
  • 1970-01-01
  • 2012-02-21
相关资源
最近更新 更多