Rijndael-256 in CBC modus mit 32 Byte Key


#1

Hallo,
erst einmal wünsche ich euch allen ein gesundes neues Jahr! :slight_smile:

Nun zu meinem Problem. Ich muss folgende zwei Methoden aus PHP in Java umsetzten. Diese beiden Methoden dienen der Ver- und Entschlüsselung von Zeichenketten mittels Rijndael-256 im CBC Modus. Hier wird speziell ein 32stelliger Schlüssel verwendet.

//!!!! PHP Funktionen !!!!
/**
 * Use this function to encrypt raw XML data (eg. your reports)
 * @param $encrypt (string needed to be encrypted)
 * @param $key (the secret key provided by REMIT and is 32 characters of lowercase letters and digits)
 * @return encrypted string - eg. encrypted XML report
 */
function mc_encrypt($encrypt, $key){
    $encrypt = serialize($encrypt);
    $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC), MCRYPT_DEV_URANDOM);
    $mac = hash_hmac('sha256', $encrypt, substr(bin2hex($key), -32));
    $passcrypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $encrypt.$mac, MCRYPT_MODE_CBC, $iv);
    $encoded = base64_encode($passcrypt).'|'.base64_encode($iv);
    return $encoded;
}

/**
 * Use this function to decrypt encrypted data (eg. encrypted XML receipts from ACER/GIE)
 * @param $decrypt (string that needs to be decrypted)
 * @param $key (the secret key provided by REMIT and is 32 characters of lowercase letters and digits)
 * @return raw XML/string - eg. decrypted XML receipts from ACER/GIE
 */
function mc_decrypt($decrypt, $key){
    $decrypt = explode('|', $decrypt.'|');
    $decoded = base64_decode($decrypt[0]);
    $iv = base64_decode($decrypt[1]);
    if(strlen($iv)!==mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC)){ return false; }
    $decrypted = trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $decoded, MCRYPT_MODE_CBC, $iv));
    $mac = substr($decrypted, -64);
    $decrypted = substr($decrypted, 0, -64);
    $calcmac = hash_hmac('sha256', $decrypted, substr(bin2hex($key), -32));
    if($calcmac!==$mac){ return false; }
    $decrypted = unserialize($decrypted);
    return $decrypted;
}

Zu Beginn wollte ich mich zunächst auf die Entschlüsselung von Nachrichten konzentrieren, da ich diese Aufgabe primär mit Java umsetzen muss.
Leider stoße ich immer wieder an die Grenzen der Crypto-Funktionen von Java und weiß nicht, wie ich diese umgehen kann. Wenn ich eine Entschlüsselung starte, erhalte ich immer folgende Fehlermeldung.

Exception in thread “main” java.security.InvalidAlgorithmParameterException: IV must be 16 bytes long.
at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineInit(Unknown Source)
at javax.crypto.Cipher.implInit(Cipher.java:806)
at javax.crypto.Cipher.chooseProvider(Cipher.java:864)
at javax.crypto.Cipher.init(Cipher.java:1396)
at javax.crypto.Cipher.init(Cipher.java:1327)
at test.Test.main(Test.java:39)

Dank Google bin ich weiß ich, das im Java die Schlüssel nicht länger als 16 Zeichen im Standard sein sollen. Wenn man größere Schlüssel verwenden will/muss, dann muss man auf alternative Biblioteken ausweisen. Ich habe es mit BouncyCastle versuche. Aber auch hier kein Erfolg.

Mein QuellCode ist folgender:

package test;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;

import java.security.GeneralSecurityException;
import java.security.Security;

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



public class Test {
	private static final String KEY = "2fe0213d48bb0ca63cb7a502dd7d10f5";

	public static void main(String[] args) throws GeneralSecurityException {
		//Nachricht im Klartext: "das ist ein Test";
		String base64Msg = "QktHTUVNeUJ1M09mRXhaUGRMU20vOFlRY1d0NzI1Qy9nY0VRcGxENS80QURGcVFzT1N3QXoyOWhGRTU3ZjIydUFFYU51U2M0MlFqN2Noc3NsYUQ5ZHY5VHRGdW53dE0rQXZaZzcwdWtUMUZycnJIb2ZEYldIN1pFeW5Yb3RvLy98a3V6TWs2Y2ExRGswcHdaL1dFRFdIK0tsbURzUm5uLzErSUZ3RXBkY1o0bz0=";

		byte[] oriMsg = Base64.decode(base64Msg);
		System.out.println(new String(oriMsg) + "\n\n");

		String[] dataParts = (new String(oriMsg)).split("\\|");
		String rawIv = dataParts[1].trim();
		byte[] iv = Base64.decode(rawIv);
		String rawMsg = dataParts[0].trim();
		byte[] msg = rawMsg.getBytes();

		System.out.println(new String(iv) + "\n"+String.valueOf(iv.length)+"\n");
		System.out.println(new String(msg) + "\n\n");

		Security.addProvider(new BouncyCastleProvider());
		Cipher cipher = Cipher.getInstance("Rijndael/CBC/PKCS7Padding");
		
		SecretKeySpec key = new SecretKeySpec(new byte[256 / Byte.SIZE],KEY);
		IvParameterSpec ivPS = new IvParameterSpec(iv);
		
		cipher.init(Cipher.DECRYPT_MODE, key, ivPS);
		byte[] out = cipher.doFinal(msg);
		System.out.println(new String(out));
		
	}
}

Könntet ihr mir bitte weiterhelfen?

Danke!
Grüße Hans

Nachtrag:
Bevor ich Ärger bekomme, das ich hier einen privaten Schlüssel veröffentlicht habe, sage ich gleich, das dieser frei erfunden und nur für die Forumsdiskussion erstellt wurde :wink:


#2

Das dürfte auch mit “reinem” Java kein Problem sein, wenn du die JCE unlimited strength policy installierst.

Deinen Quellcode sehe ich mir später am PC mal genauer an.

Beim grob drüberschauen fällt auf, dass deine Nachricht nicht so wie der Ciphertext vom PHP-Code aufgebaut ist (2x Base64-String durch | getrennt). Außerdem dürfte in Java der Key als Bytearray und nicht als Hexstring übergeben werden müssen.


#3

Die JCE unlimited strength policy habe ich bereits installiert. Dennoch bekomme ich den Fehler.

Der String ist korrekt. Der String ist für die Datenübertragung in Summe noch einmal mittels Base64 kodiert wurden. Das heißt, das ich den String erst einmal über Base64 dekodiere und dann die notwendige String-Form vorliegen habe, so wie ihn die PHP Funktion erwartet.
byte[] oriMsg = Base64.decode(base64Msg);


#4

Ich habe deinen Code jetzt mal laufen lassen. Der IV ist zu lang. Unabhängig von der Schlüssellänge, ist dieser bei AES immer 128 Bit lang (16 Byte), weil AES mit 128 Bit langen Blöcken arbeitet. Dein IV ist 256 Bit lang, das passt nicht zusammen.

Außerdem hat mir die Dokumentation von der PHP-Funktion mcrypt_encrypt verraten, dass ein Null-Padding verwendet wird. Das passt nicht zum PKCS7-Padding in der Java-Version.
Und, wie oben schon angemerkt, ergibt dein Key wenig Sinn. Der Schlüssel besteht nur aus einem 128-Bit Schlüsselraum, sofern er wirklich so verwendet wurde. Die PHP-Funktion erwartet an der Stelle einen Binärstring, sodass das durchaus möglich wäre.


#5

Danke für deine Hinweise. Ich habe AES und Rijndael immer als äquivalent angesehen. Dem scheint nicht so zu sein :-(, hier gilt es für mich jetzt eine Bildungslücke zu schließen ;-).

Bei Rijndael kann man scheinbar auch Initialisierungsvektoren mit 16, 24 oder 32 Byte verwenden.
Wie würde das dann im Java aussehen?

Ich habe aus dem Titel das Schlagwort “AES” entfernt, da es hier doch nicht zutreffend ist.


#6

Die Antwort hier sieht brauchbar aus, zumal du sowieso schon BouncyCastle verwendest:


Edit: die Antwort darunter berücksichtigt auch noch das ZeroBytePadding:


#7

Vielen Dank für diese Hinweise. Das sieht sehr gut aus. Erste Test waren vielversprechend :slight_smile: