【问题标题】:AES rijndael encrypt between c and javac和java之间的AES rijndael加密
【发布时间】:2012-05-02 03:17:57
【问题描述】:

我越来越疯狂地在 c 和 Java 之间加密/解密,但到目前为止,Java 中的加密字符串和 c 中的加密字符串看起来不一样。 我已经研究过 base64 编码/解码,但是在疯狂地为 java 和 c 找到一个库之后,各自的 base64 结果看起来不同了! 我认为这是在 Java UTF16 字符串之间转换、在 java 中保存 Byte 或 AES 选项(128/256 密钥、PK5 填充或谁知道是什么)之间转换的问题,或者可能是终端的 UTF8 转换或上述的荒谬组合。 到目前为止,我得到:

  user1@comp1:~/Desktop$ gcc AES.c /usr/lib/libmcrypt.a -lssl -lcrypto -lpthread
  user1@comp1:~/Desktop$ /usr/java/jdk1.6.0_25/bin/javac AES.java
  user1@comp1:~/Desktop$ ./a.out 
      Before encryption: test text 123
      After encryption: 49 -60 66 43 -8 66 -106 0 -14 -44 3 47 65 127 -110 117 
      After decryption: test text 123
  user1@comp1:~/Desktop$ java AES 
     Before encryption: test text 123
     After encryption: -110 21 23 59 47 120 70 -93 -54 -93 -12 -70 -91 83 -113 85 
     After decryption: test text 123

我想我真的需要有人帮助我进行低级编码,下面分别是 Java 和 c 的代码:

import java.security.MessageDigest;
import java.util.Arrays;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AES {
  public static void main(String [] args) {
      try {
  String text = "test text 123";
  /*fixed here now it is 128 bits = 16 Bytes*/
  String encryptionKey = "E072EDF9534053A0";

  System.out.println("Before encryption: " + text);

  byte[] cipher = encrypt(text, encryptionKey);

  System.out.print("After encryption: ");
  for (int i=0; i<cipher.length; i++)
        System.out.print(new Integer(cipher[i])+" ");
  System.out.println("");

  String decrypted = decrypt(cipher, encryptionKey);

  System.out.println("After decryption: " + decrypted);

      } catch (Exception e) {
  e.printStackTrace();
      } 
  }

  public static byte[] encrypt(String plainText, String encryptionKey) throws Exception {
      Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE");
      SecretKeySpec key = new SecretKeySpec(encryptionKey.getBytes("UTF-8"), "AES");
      cipher.init(Cipher.ENCRYPT_MODE, key,new IvParameterSpec(new byte[cipher.getBlockSize()]));
      return cipher.doFinal(plainText.getBytes("UTF-8"));
  }

  public static String decrypt(byte[] cipherText, String encryptionKey) throws Exception{
      Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE");
      SecretKeySpec key = new SecretKeySpec(encryptionKey.getBytes("UTF-8"), "AES");
      cipher.init(Cipher.DECRYPT_MODE, key,new IvParameterSpec(new byte[cipher.getBlockSize()]));
      return new String(cipher.doFinal(cipherText),"UTF-8");
  }
  }

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mcrypt.h>

#include <math.h>
#include <stdint.h>
#include <stdlib.h>

int main()
{
MCRYPT td, td2;
const char * plaintext = "test text 123";
int i;
char *key; /* created using mcrypt_gen_key */
char *IV;
char * block_buffer;
int blocksize;
int keysize = 16; /* 128 bits == 16 bytes */
size_t* sizet;

key = calloc(1, keysize);

/*below dirty trick to be sure the entire key has been padded with \0's */
strcpy(key, "E072EDF9534053A0");
memset(key, '\0', sizeof(key));
strcpy(key, "E072EDF9534053A0");

/*  MCRYPT mcrypt_module_open( char *algorithm, char* algorithm_directory, char* mode, char* mode_directory);
 * This function normally returns an encryption descriptor, or MCRYPT_FAILED on error. 
 */
td = mcrypt_module_open("rijndael-128", NULL, "cbc", NULL);
/*we need two encryption descriptors td and td2 for decryption*/
td2 = mcrypt_module_open("rijndael-128", NULL, "cbc", NULL);

blocksize = mcrypt_enc_get_block_size(td);
block_buffer = calloc(1, blocksize);
/*below to be sure the entire block_buffer has been padded with \0's */
memset(block_buffer, '\0', blocksize);

IV = malloc(mcrypt_enc_get_iv_size(td));
if ((block_buffer == NULL) || (IV == NULL)) {
fprintf(stderr, "Failed to allocate memory\n");
exit(EXIT_FAILURE);
}
for (i = 0; i < mcrypt_enc_get_iv_size(td); i++) {
IV[i] = 0;
}
/*as we can see both td and td2 get same key and IV*/
mcrypt_generic_init(td, key, keysize, IV);
mcrypt_generic_init(td2, key, keysize, IV);

memset(block_buffer, '\0', sizeof(plaintext));
strcpy(block_buffer, plaintext);

printf("Before encryption: %s\n", block_buffer);
mcrypt_generic (td, block_buffer, blocksize);

printf("After encryption: ");
for (i=0; i < blocksize; i++)
    printf("%d ", block_buffer[i]);
printf("\n");

mdecrypt_generic (td2, block_buffer, blocksize);
printf("After decryption: %s\n", block_buffer);

/* deinitialize the encryption thread */
mcrypt_generic_deinit (td);
mcrypt_generic_deinit(td2);
/* Unload the loaded module */
mcrypt_module_close(td);
mcrypt_module_close(td2);
return 0;
}

【问题讨论】:

  • 如果我不够清楚,我希望打印到终端的加密字符串在 Java 和 c 输出中看起来相同,这样我稍后将设法在 Java 客户端和 c 服务器之间发送它反过来。
  • 您也可以将加密数据打印为十六进制(更容易)
  • 我试过了,但是在 c 中打印到十六进制并不是那么简单(我在这里看到了关于 stackoverflow 的讨论,其中有 10 种不同的解决方案可以做到这一点,甚至没有一个提到标准 c 函数!)。除了那个十六进制不是很有效这就是我第一次尝试base64编码的原因,如果你可以让以上两个使用十六进制,我会很感激
  • @dendini 如果你使用 unix(你应该!)你可以使用命令 hexdump 作为过滤器。
  • AES 是 RIJNDAEL-128,而不是 256?

标签: java c cryptography aes mcrypt


【解决方案1】:

总结

解决所有问题后,我得到:

$ ./a.out
==C==
plain:   test text 123
cipher:  16 -124 41 -83 -16 -123 61 -64 -15 -74 87 28 63 30 64 78 
decrypt: test text 123

$java AES
==JAVA==
plain:   test text 123
cipher:  16 -124 41 -83 -16 -123 61 -64 -15 -74 87 28 63 30 64 78 
decrypt: test text 123

代码见下文。

问题

  1. 密码错误: AES 是 Rijndael-128,这是 Java 加密使用的。但是,您的 C 代码指定了 Rijndael-256,它不是 AES。来自Mcrypt docs

    Rijndael [...] AES(如果在 128 位模式下使用)

    请记住,当将密码称为 CIPHER-XXX 时,XXX 指的是块大小,而不是密钥长度。事实上,Rijndael-128 将接受 128、192 和 256 位的密钥。然而,AES 严格指 128 位 Rijndael。你会发现更多信息at the usual place

  2. 内存初始化不正确:您没有在 C 中正确初始化内存,这导致消息结尾和块限制之间的字节未定义。

  3. 不正确的填充:您在 java 代码中指定了 PKCS5Padding,但您没有在 C 中适当地填充您的明文。PKCS5 实际上非常简单,并且在 the wiki 上描述得很好。要使用 PKCS#5 进行填充,只需确保每个填充字节都等于填充的总长度:

    ... | DD DD DD DD DD DD DD DD | DD DD DD DD 04 04 04 04 |
    ... | DD DD DD DD DD DD DD DD | DD DD DD DD DD 03 03 03 |
    ... | DD DD DD DD DD DD DD DD | DD DD DD DD DD DD 02 02 |
    etc...
    

    其中 DD 是数据字节。还有其他填充方法,即explained elsewhere

  4. 不正确的密钥处理:您从十六进制编码的字符串中提取 Java 程序的密钥,而在您的 C 程序中,您直接将十六进制字符串作为您的密钥。您必须一致地处理您的密钥,两个程序才能提供相同的输出。

  5. 两边的初始化向量不同:您需要两边的初始化向量相同。随机生成您的 IV 是正确的。然后应该将 IV 与密文一起传递,以便在另一侧进行解密。 IV不应重复使用。

示例程序

以下内容可通过github获得

import java.security.MessageDigest;
import java.util.Arrays;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AES {
  /*
   * Please realise that the following IV is terrible.
   * (As easy to crack as ROT13...)
   * Real situations should use a randomly generated IV.
   */
  static String IV = "AAAAAAAAAAAAAAAA";
  /* 
   * Note null padding on the end of the plaintext.
   */
  static String plaintext = "test text 123\0\0\0"; 
  static String encryptionKey = "0123456789abcdef";
  public static void main(String [] args) {
    try {

      System.out.println("==JAVA==");
      System.out.println("plain:   " + plaintext);

      byte[] cipher = encrypt(plaintext, encryptionKey);

      System.out.print("cipher:  ");
      for (int i=0; i<cipher.length; i++){
        System.out.print(new Integer(cipher[i])+" ");
      }
      System.out.println("");

      String decrypted = decrypt(cipher, encryptionKey);

      System.out.println("decrypt: " + decrypted);

    } catch (Exception e) {
      e.printStackTrace();
    } 
  }

  public static byte[] encrypt(String plainText, String encryptionKey) throws Exception {
    Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding", "SunJCE");
    SecretKeySpec key = new SecretKeySpec(encryptionKey.getBytes("UTF-8"), "AES");
    cipher.init(Cipher.ENCRYPT_MODE, key,new IvParameterSpec(IV.getBytes("UTF-8")));
    return cipher.doFinal(plainText.getBytes("UTF-8"));
  }

  public static String decrypt(byte[] cipherText, String encryptionKey) throws Exception{
    Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding", "SunJCE");
    SecretKeySpec key = new SecretKeySpec(encryptionKey.getBytes("UTF-8"), "AES");
    cipher.init(Cipher.DECRYPT_MODE, key,new IvParameterSpec(IV.getBytes("UTF-8")));
    return new String(cipher.doFinal(cipherText),"UTF-8");
  }
}

还有C文件:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*
 * MCrypt API available online:
 * http://linux.die.net/man/3/mcrypt
 */
#include <mcrypt.h>

#include <math.h>
#include <stdint.h>
#include <stdlib.h>

int encrypt(
    void* buffer,
    int buffer_len, /* Because the plaintext could include null bytes*/
    char* IV, 
    char* key,
    int key_len 
){
  MCRYPT td = mcrypt_module_open("rijndael-128", NULL, "cbc", NULL);
  int blocksize = mcrypt_enc_get_block_size(td);
  if( buffer_len % blocksize != 0 ){return 1;}

  mcrypt_generic_init(td, key, key_len, IV);
  mcrypt_generic(td, buffer, buffer_len);
  mcrypt_generic_deinit (td);
  mcrypt_module_close(td);

  return 0;
}


int decrypt(
    void* buffer,
    int buffer_len,
    char* IV, 
    char* key,
    int key_len 
){
  MCRYPT td = mcrypt_module_open("rijndael-128", NULL, "cbc", NULL);
  int blocksize = mcrypt_enc_get_block_size(td);
  if( buffer_len % blocksize != 0 ){return 1;}

  mcrypt_generic_init(td, key, key_len, IV);
  mdecrypt_generic(td, buffer, buffer_len);
  mcrypt_generic_deinit (td);
  mcrypt_module_close(td);

  return 0;
}

void display(char* ciphertext, int len){
  int v;
  for (v=0; v<len; v++){
    printf("%d ", ciphertext[v]);
  }
  printf("\n");
}

int main()
{
  MCRYPT td, td2;
  char * plaintext = "test text 123";
  char* IV = "AAAAAAAAAAAAAAAA";
  char *key = "0123456789abcdef";
  int keysize = 16; /* 128 bits */
  char* buffer;
  int buffer_len = 16;

  buffer = calloc(1, buffer_len);
  /* 
   * Note that calloc() will null-initialise the memory. (Null padding)
   */

  strncpy(buffer, plaintext, buffer_len);

  printf("==C==\n");
  printf("plain:   %s\n", plaintext);
  encrypt(buffer, buffer_len, IV, key, keysize); 
  printf("cipher:  "); display(buffer , buffer_len);
  decrypt(buffer, buffer_len, IV, key, keysize);
  printf("decrypt: %s\n", buffer);

  return 0;
}

使用最新的 Libmcrypt 和 Java 1.7 在 Linux 上进行测试。注意,因为我写的 C 很仓促,而且它充满了内存泄漏和溢出问题。 (正如他们所说,练习留给读者清理它......)

【讨论】:

  • 我尝试了 NoPadding 并收到错误 javax.crypto.IllegalBlockSizeException: Input length not multiple of 16 bytes,而 c 代码工作正常,所以我认为 mcrypt 做了一些填充,并假设它是 PKCS5Padding ! rijndael 128 256 在代码错误之前我已经回答了,但我在发布之前已经检查过了,但结果仍然不同..
  • 更新了另一个可能的原因。为什么在 Java 中将密钥从十六进制转换为字节,而不是在 C 中?
  • @brice:AES 的密钥大小也为 256。为什么你说 256 不是 AES
  • 块大小固定为 128 位。不是键大小(128、192、256)。它在the wikipedia page
  • @dendini 你对上面有什么好运气吗?
【解决方案2】:

除了@brice 的标志之外,我看不到您在Java 代码中初始化IV 的位置。您创建了一个“IvParameterSpec”,但传入了一个全零字节数组。

您的 C 代码会生成一个随机 IV,因此每次运行时都应生成不同的密文。

尝试对两种实现都使用固定的 IV,看看这是否会给您带来一致的结果。当然,当您想要进行真正的加密时,您需要再次生成一个随机 IV,但使用固定的 IV 可能会帮助您进行调试。

我还要确保两个实现都使用相同的填充(通过显式设置填充而不是让 mcrypt 选择),而不是将原始密文写入控制台,我强烈建议写入十六进制值,或者只是将每个字节写成一个数字——当您不必担心不可打印的字符时,调试起来会容易得多。这只是为了调试,所以看起来效率不高或不愉快也没关系。

【讨论】:

  • 好收获。如果 IV 不同,这将永远不会起作用。
  • 其实IV只要是随机IV即可,解密和加密时不一定要相等,否则需要知道密文、秘钥和IV向量解密,显然不是这样。我尝试在Java中使用不同的IV进行加密和解密,但没有效果,密文保持不变..
  • @Dendini:确实是这样。您需要使用与加密相同的 IV 进行解密。如果它在您的 Java 实现中没有这样做,那么您做错了什么。请注意,您的 C 实现尝试生成一个新的 IV 进行解密,但它没有被使用,因为您已经使用加密 IV 初始化了 td2
  • @dendini 认为您需要再看看how block ciphers work。我强烈推荐Applied Cryptography的第9章
  • IV 向量不必相同,您是否尝试过我上面放置的代码在加密后更改 IV 以解密密文?你会看到密文被正确解密所以IV不是这里的重点..(无论如何我都会更新上面的IV代码只是为了让任何困惑消失)
猜你喜欢
  • 2023-03-23
  • 2011-03-26
  • 2010-10-19
  • 1970-01-01
  • 2017-05-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-23
相关资源
最近更新 更多