我正在使用java安全性和openssl来使用aes-ecb-256加密特定的字符串,但结果并不相同。 是否有任何用于实现Rijndael's key schedule的java的lib?
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 。这两段代码的结果是不同的。
答案 0 :(得分:2)
填充不同。您的C ++(非C)代码隐式填充(来自calloc)。您的Java代码指示JCE使用PKCS#5填充,这绝不是零。 (讽刺的是,对于像AES那样的16字节块,它实际上是PKCS#7而不是PKCS#5,但几乎每个人都称它为PKCS#5。)
密钥不同。您的C ++代码使用16个ASCII&#39; a&#39; (十六进制0x61)作为键。您的Java代码使用随机生成的密钥,每次都会有不同的密钥(概率非常高)。
无论如何,ECB和确定性加密都很糟糕。除非您的数据是高度熵(理想情况下完全随机且通常也很小),否则ECB是个坏主意。有关基本解释,请参阅http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation和(in)着名的ECB企鹅。 或者为ECB搜索crypto.SE或security.SE,你会发现几十个答案一遍又一遍地解释,为什么ECB是坏的。更一般地,任何非随机加密都是不好的做法,因为它允许对手区分两个密文是否是相同的明文,即使不知道明文是什么。
您的错误处理效果不佳。您的C ++代码会输出错误并退出,但退出状态为-1
。这在不同的系统上给出了不同的结果,在我使用的系统上从不-1。 0到略小于127(比如说120)可移植到所有Unices和Windows,但只有0和标准C保证来自EXIT_SUCCESS
的两个值EXIT_FAILURE
和stdlib.h
。您的Java代码打印堆栈跟踪但随后继续执行丢失或无效的数据,这些数据根本无法正常工作。它也应该退出 - 或者对于一次性代码,你可以省略异常处理程序;未处理异常的JVM默认值是打印堆栈跟踪并退出。
旁白:连接Integer.toHexString(val)
会产生无法使用的结果。字节12 03将产生123,因此字节01 23;在阅读123时,您无法分辨实际数据。添加分隔符,或者更好(更一致)使用String.format ("%02x", val)
,就像对C一样。并且你不需要演员表;在Java表达式中,提升小于int的整数类型。 (同样在C和C ++中,但它们稍微复杂一点,因为它们必须处理所有等级的有符号和无符号整数类型。除了char
之外,Java整数都是有符号的。)
当你有一个Key对象时,在Java加密中,你 需要将它转换为KeySpec才能用于加密。事实上,通常它是另一种方式;对于像RSA这样的结构化算法,您只能 使用密钥,如果您有KeySpec,则必须进行非常重要的转换(通常由工厂)才能获得密钥。但由于秘密/对称密钥只是位,所以SecretKeySpec既是KeySpec又是密钥。
编辑: JCE不会为你做零填充,因为它对一般数据不安全;你不能一直解密你加密的相同数据,Java设计者和大多数用户认为这是一个缺陷。由于您不关心您的数据,因此您可以使用零填充 - 使用标准库类java.util.Arrays
可以轻松地使用Java进行填充
// 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中使用的密钥是32个字节(每个8位)的ASCII代码,用于&#39; a&#39; - 除非您使用EBCDIC运行机器,但如果是这样,您就会知道它。 (确切地说,OpenSSL使用C和C ++类型unsigned char
,其中可以超过8位,但OpenSSL仅支持8位的平台/目标。)Java加密使用byte
的数组,它是8位(但已签名)。所以你需要一个32字节的数组,每个数组包含&#39; a&#39;。你可以从String
"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');
如果密钥中存在或可能(曾经)是非ASCII字符,则注意String.getBytes(/*no-args*/)
密钥是不安全的。 no-args重载使用默认编码,该编码可能取决于平台和其他环境因素,例如用户和区域设置;这意味着它可能在您解密时产生与加密时不同的密钥字节,并且使用不同的(因此错误的)密钥字节失败。如果你需要在ASCII之外使用字符(如重音字母),你必须使用(两个)重载中的一个指定&#34; Charset&#34; (用于编码的Java),如果这是一个多字节编码(UTF8是主要的例子),你需要确保值是字节中的正确长度而不是(必须)在字符中。更好的是首先将密钥视为一个字节序列而不是字符。