AES rijndael шифрует между C и Java
Я схожу с ума от шифрования / дешифрования между c и Java, но пока зашифрованная строка в Java и та, что в c, не выглядят одинаково. Я исследовал кодирование / декодирование base64, но после того, как сошел с ума, чтобы найти библиотеку для java и c, соответствующие результаты base64 выглядели иначе! Я думаю, что это проблема преобразования между строкой Java UTF16, сохранения байта в java или, возможно, параметров 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;
}
2 ответа
Резюме
После решения всех вопросов я получаю:
$ ./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
Смотрите код ниже.
вопросы
Неправильный шифр: AES - это Rijndael-128, который использует шифрование Java. Тем не менее, ваш код на C указывает Rijndael-256, который не является AES. Из документов Mcrypt:
Rijndael [...] AES, если используется в 128-битном режиме
Помните, что при упоминании шифра CIPHER-XXX XXX относится к размеру блока, а не к длине ключа. Действительно, Rijndael-128 будет принимать ключи 128, 192 и 256 бит. AES, однако, относится строго к 128-битному Rijndael. Вы найдете больше информации в обычном месте.
Неправильная инициализация памяти: Вы неправильно инициализируете свою память в C, что оставляет байты между концом вашего сообщения и пределом блока неопределенным.
Неверное заполнение: Вы указываете
PKCS5Padding
в вашем Java-коде, но вы не добавляете свой открытый текст соответствующим образом в C. PKCS5 на самом деле тривиально прост и достаточно хорошо описан в вики. Чтобы заполнить 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 - байты данных Существуют и другие методы заполнения, которые описаны в другом месте.
Неправильная обработка ключа: вы извлекаете ключ для вашей Java-программы из шестнадцатеричной кодированной строки, тогда как в вашей C-программе вы берете шестнадцатеричную строку непосредственно в качестве ключа. Вы должны обращаться со своими ключами последовательно для обеих программ, чтобы дать тот же результат.
Различные векторы инициализации с обеих сторон. Вам понадобится один и тот же вектор инициализации с обеих сторон. Случайное создание вашего 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;
}
Протестировано на Linux с последними версиями Libmcrypt и Java 1.7. Остерегайтесь, так как я написал C в спешке, и он полон утечек памяти и проблем переполнения. (Упражнение, оставленное читателю, чтобы очистить это, как они говорят...)
Помимо флагов @brice, я не вижу, где вы инициализируете IV в своем Java-коде. Вы создаете `IvParameterSpec', но вы передаете массив нулевых байтов.
Ваш код на C генерирует случайный IV, поэтому он должен создавать новый зашифрованный текст при каждом запуске.
Попробуйте использовать фиксированный IV для обеих реализаций и посмотрите, дает ли это согласованные результаты. Конечно, вам нужно будет снова генерировать случайный IV, если вы хотите сделать реальное шифрование, но использование фиксированного IV может помочь вам с отладкой.
Я бы также удостоверился, что обе реализации используют один и тот же отступ (путем явной установки отступа, а не разрешения выбора mcrypt), и вместо записи необработанного зашифрованного текста в консоль я настоятельно рекомендую записать шестнадцатеричные значения или просто записать каждый байт. как число - отладка будет значительно проще, если вам не придется беспокоиться о непечатных символах. Это только для отладки, поэтому не имеет значения, на что это неэффективно или приятно смотреть.