2

私は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;
}
4

2 に答える 2

13

概要

すべての問題を解決した後、私は次のようになります。

$ ./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ドキュメントから:

    Rijndael[...]128ビットモードで使用する場合のAES

    暗号をCIPHER-XXXと呼ぶ場合、XXXはキーの長さではなくブロックサイズを指すことに注意してください。実際、Rijndael-128は、128、192、および256ビットのキーを受け入れます。ただし、AESは厳密に128ビットのRijndaelを指します。あなたはいつもの場所でより多くの情報を見つけるでしょう。

  2. 誤ったメモリ初期化: Cでメモリを適切に初期化しないため、メッセージの終わりとブロック制限の間のバイトが未定義のままになります。

  3. 誤ったパディング: Javaコードで指定PKCS5Paddingしますが、Cではプレーンテキストを適切にパディングしません。PKCS5は実際には簡単で、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はデータバイトです。他の場所で説明されている他のパディング方法があります。

  4. 誤ったキー処理: Javaプログラムのキーを16進エンコードされた文字列から抽出しますが、Cプログラムでは、16進文字列を直接キーとして使用します。同じ出力を得るには、両方のプログラムでキーを一貫して処理する必要があります。

  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とJava1.7を使用してLinuxでテスト済み。私は急いでCを書いたので、気をつけてください、そしてそれはメモリリークとオーバーフローの問題でいっぱいです。(彼らが言うように、それをきれいにするために読者に残された運動...)

于 2012-04-20T14:31:51.823 に答える
2

@briceのフラグに加えて、JavaコードのどこでIVを初期化するかわかりません。`IvParameterSpec'を作成しますが、すべてゼロのバイト配列を渡します。

CコードはランダムなIVを生成するため、実行するたびに異なる暗号文を生成する必要があります。

両方の実装に固定IVを使用してみて、一貫した結果が得られるかどうかを確認してください。もちろん、実際の暗号化を行う場合は、ランダムIVを再度生成する必要がありますが、固定IVを使用するとデバッグに役立つ場合があります。

また、両方の実装が同じパディングを使用していることを確認し(mcryptに選択させるのではなく、パディングを明示的に設定することにより)、コンソールに生の暗号文を書き込む代わりに、16進値を書き込むか、各バイトを書き込むことを強くお勧めします数字として-印刷できない文字について心配する必要がない場合、デバッグが非常に簡単になります。これはデバッグ専用であるため、効率的でなくても、見た目が快適でなくてもかまいません。

于 2012-04-20T15:32:18.080 に答える