AES Encryption PyCrypto - Java AES inkompatibel?

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Mad-Marty
User
Beiträge: 317
Registriert: Mittwoch 18. Januar 2006, 19:46

Hallo,

ich versuche im moment einen cipher zwischen Python und Java zu verwenden, das funktioniert aber nicht.

Bei dem java ergebnis hängen immer 16 byte mehr hinten dran.
Schlimmer noch, wenn Java das Python ergebnis deciphern soll gibt es eine 'Given final block not properly padded' exception.



Zuerst das Java Cipher:

Code: Alles auswählen

 
   import java.security.*;
   import javax.crypto.*;
   import javax.crypto.spec.*;
   import java.io.*;

public class AES{
		public static byte[] hexStringToByteArray(String s) {
		    int len = s.length();
		    byte[] data = new byte[len / 2];
		    for (int i = 0; i < len; i += 2) {
		        data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
		                             + Character.digit(s.charAt(i+1), 16));
		    }
		    return data;
		}


	     public static String asHex (byte buf[]) {
	      StringBuffer strbuf = new StringBuffer(buf.length * 2);
	      int i;

	      for (i = 0; i < buf.length; i++) {
	       if (((int) buf[i] & 0xff) < 0x10)
		    strbuf.append("0");

	       strbuf.append(Long.toString((int) buf[i] & 0xff, 16));
	      }

	      return strbuf.toString();
	     }

	     public static void main(String[] args) throws Exception {
	       SecretKeySpec skeySpec = new SecretKeySpec("abcdefghijklmnop".getBytes(), "AES");
	       Cipher cipher = Cipher.getInstance("AES");
	       cipher.init(Cipher.ENCRYPT_MODE, skeySpec);

	       byte [] input = "AAAAAAAAAAAAAAAA".getBytes();
	       byte[] encrypted = cipher.doFinal(input);
	       System.out.println("encrypted string: " + asHex(encrypted));

	       cipher.init(Cipher.DECRYPT_MODE, skeySpec);
	       byte[] original = cipher.doFinal(hexStringToByteArray("8c155e4e9bf78570ee6300dc5f8a418a")); // << raises 
	       
	       String originalString = new String(original);
	       System.out.println("Original string: " +
	         originalString + " " + asHex(original));
	     }
	   }
Gibt aus: encrypted string: 8c155e4e9bf78570ee6300dc5f8a418a8e64ce873f174dbb2423fcd814580e15


Jetzt das Python Cipher:
from Crypto.Cipher import AES

Code: Alles auswählen

plain = "A"*16
obj=AES.new('abcdefghijklmnop', AES.MODE_ECB)
print binascii.hexlify(obj.encrypt(plain))
gibt aus : 8c155e4e9bf78570ee6300dc5f8a418a



Dann das ganze mal decoded mit python ergibt:

Code: Alles auswählen

>>> obj.decrypt(binascii.unhexlify('8c155e4e9bf78570ee6300dc5f8a418a'))
'AAAAAAAAAAAAAAAA'
>>> obj.decrypt(binascii.unhexlify('8c155e4e9bf78570ee6300dc5f8a418a8e64ce873f174dbb2423fcd814580e15'))
'AAAAAAAAAAAAAAAA\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'

Das Java scheint also extra zu padden .... Aber warum nur ... der input ist ja 16 byte.
lunar

Ohne den Quellcode detailliert untersucht zu haben, würde ich erstmal darauf tippen, dass Java UCS-2-Unicode-Strings nutzt, und somit ein Zeichen in zwei Bytes repräsentiert, während dein Python-Code Bytestrings nutzt. Das würde erklären, warum die Ausgabe des Java-Programms doppelt so groß ist wie die Ausgabe des Python-Programms.

Mich würde aber interessieren, was du damit erreichen willst? Ein gemeinsam genutztes kryptographisches Protokoll ist komplizierter als dein Code, wie du gemerkt hast. Man muss für ein einheitliches Encoding sorgen, die Schlüsselaustausch bewältigen, die Daten für die genutzte Chiffre aufbereiten (was bei Blockalgorithmen vor allem sicheres Padding bedeutet), etc.

Summa summarum fährst du besser, wenn du etwas existierendes nutzt. Für Dateien bietet sich GPG an, für Netzwerkübertragungen OpenSSL oder SSH.
Mad-Marty
User
Beiträge: 317
Registriert: Mittwoch 18. Januar 2006, 19:46

lunar hat geschrieben:Ohne den Quellcode detailliert untersucht zu haben, würde ich erstmal darauf tippen, dass Java UCS-2-Unicode-Strings nutzt, und somit ein Zeichen in zwei Bytes repräsentiert, während dein Python-Code Bytestrings nutzt. Das würde erklären, warum die Ausgabe des Java-Programms doppelt so groß ist wie die Ausgabe des Python-Programms.
Definitiv nicht, siehe decoded werte
Y0Gi
User
Beiträge: 1454
Registriert: Freitag 22. September 2006, 23:05
Wohnort: ja

Naja, wenn du willst, dass es läuft, dann padde die Strings doch einfach auf die benötigte Länge mit dem entsprechenden Zeichen. Dann sollte der Java-Part es auch wieder entziffern können.
Mad-Marty
User
Beiträge: 317
Registriert: Mittwoch 18. Januar 2006, 19:46

Y0Gi hat geschrieben:Naja, wenn du willst, dass es läuft, dann padde die Strings doch einfach auf die benötigte Länge mit dem entsprechenden Zeichen. Dann sollte der Java-Part es auch wieder entziffern können.

So einfach ist das nicht. Python kann das Java nicht decrypten und umgekehrt.
Und ohne decrypten weiss man auch wieder nicht wie lang der inhalt ist.

Die benötigte Blocklänge beim AES ist 16. Der String ist ebenfalls 16 Byte.
Java Padded scheinbar obwohl es auf einer 16 byte grenze liegt (und füllt damit einen zusätzlichen Block).

Und das auch noch falsch. Irgendwo meine ich gelesen zu haben das mit 0x08 gepadded werden soll ... java nimmt 0x10 wie es aussieht.

Verwendet wird übrigens die JDK 5.
BlackJack

Vielleicht bestimmt der Java-Code die Anzahl der benötigten 16-Byte-Blöcke mittels ``data_length / 16 + 1``.
Mad-Marty
User
Beiträge: 317
Registriert: Mittwoch 18. Januar 2006, 19:46

BlackJack hat geschrieben:Vielleicht bestimmt der Java-Code die Anzahl der benötigten 16-Byte-Blöcke mittels ``data_length / 16 + 1``.
sieht leider fast so aus ... und sowas in der standard library ;)

Vielleicht liest es ja noch jemand der einen richtig guten Lösungsansatz hat.

Sich drauf zu verlassen das der letzte Block immer müll ist und diesen abzuschneiden will ich jedenfalls nicht ... das ist ein übler Hack.


EDIT: Ist das nicht sogar eine ziemlich krasse Sicherheitslücke, wenn klar ist das da 1 Block angehangen wird der als content nur 0x10 enthält?

Das müsste doch theoretisch sogar eine rekonstruktion vom Schlüssel zulassen.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

1. Benutze nie, nie, nie in Java "getBytes()", da dies ein plattformabhängiger Befehl ist. Übergib immer, immer, immer das Encoding. Das ist aber nicht der Grund, warum es schief läuft. Mit Unicode und Encodings hat das Problem nichts zu tun.

2. Du musst drei Parameter definieren: Verschlüsselungsalgorithmus, Block-Mode, Padding-Algorithmus. Das tust du nicht. Ich vermute daher, dass Java hier andere Defaults hat als Python.

Richtig ist, das AES immer Blöcke von 16 Bytes verschlüsselt. Kennt man die Länge der Daten, kann man auf Padding verzichten. Andernfalls ist es unumgänglich, oder man weiß beim Entschlüsseln nicht mehr, wo die Nutzdaten aufhören. Padding fügt immer Daten an, d.h. padded man 16 Bytes, kommen in diesem ungüstigsten Fall 16 Bytes dazu, wenn nicht, wie soll man sonst das Padding erkennen. Padding macht die Verschlüsselung übrigens nicht unsicherer.

Dies passiert eher bei dem falschen Block-Mode. CBC ist hier z.B. besser als ECB, siehe Beispiel in der Wikipedia.

Wenn Python ECB und wohl kein Padding benutzt, dann versuche bei Java mal `Cipher.getInstance("AES/ECB/NoPadding")` um den richtigen Cipher zu erzeugen.

Das benutze Padding ist übrigens PKCS7. Wenn du Python also dazu bringen kannst, dieses Padding zu benutzen, sollte es auch gehen, ohne das du bei Java etwas ändern musst. Aber prüfe nochmal, ob Java nicht standardmäßig CBC statt ECB benutzt - ich weiß das nicht mehr.

Ein Padding mit 08 kenne ich nicht - das wäre höchstens PKCS7, wenn len(Nutzdaten) mod 16 == 8.

Die Standardbibliothek von Java ist übrigens in Ordnung und zusammen mit BouncyCastle ist das IMHO eine der besten Plattformen, um mit Kryptografie zu arbeiten.

Stefan
Mad-Marty
User
Beiträge: 317
Registriert: Mittwoch 18. Januar 2006, 19:46

sma hat geschrieben:1. Benutze nie, nie, nie in Java "getBytes()", da dies ein plattformabhängiger Befehl ist. Übergib immer, immer, immer das Encoding. Das ist aber nicht der Grund, warum es schief läuft. Mit Unicode und Encodings hat das Problem nichts zu tun.

2. Du musst drei Parameter definieren: Verschlüsselungsalgorithmus, Block-Mode, Padding-Algorithmus. Das tust du nicht. Ich vermute daher, dass Java hier andere Defaults hat als Python.

Richtig ist, das AES immer Blöcke von 16 Bytes verschlüsselt. Kennt man die Länge der Daten, kann man auf Padding verzichten. Andernfalls ist es unumgänglich, oder man weiß beim Entschlüsseln nicht mehr, wo die Nutzdaten aufhören. Padding fügt immer Daten an, d.h. padded man 16 Bytes, kommen in diesem ungüstigsten Fall 16 Bytes dazu, wenn nicht, wie soll man sonst das Padding erkennen. Padding macht die Verschlüsselung übrigens nicht unsicherer.

Dies passiert eher bei dem falschen Block-Mode. CBC ist hier z.B. besser als ECB, siehe Beispiel in der Wikipedia.

Wenn Python ECB und wohl kein Padding benutzt, dann versuche bei Java mal `Cipher.getInstance("AES/ECB/NoPadding")` um den richtigen Cipher zu erzeugen.

Das benutze Padding ist übrigens PKCS7. Wenn du Python also dazu bringen kannst, dieses Padding zu benutzen, sollte es auch gehen, ohne das du bei Java etwas ändern musst. Aber prüfe nochmal, ob Java nicht standardmäßig CBC statt ECB benutzt - ich weiß das nicht mehr.

Ein Padding mit 08 kenne ich nicht - das wäre höchstens PKCS7, wenn len(Nutzdaten) mod 16 == 8.

Die Standardbibliothek von Java ist übrigens in Ordnung und zusammen mit BouncyCastle ist das IMHO eine der besten Plattformen, um mit Kryptografie zu arbeiten.

Stefan
Danke für den wirklich guten Post, Stefan.

Ich werde versuchen auf CBC und NoPadding umsteigen (Python + Java)
(und das getBytes encoding angeben).

Als provider im Java dient übrigens nicht der BouncyCastle, da ich den Standard AES provider benutze, der sollte ja auch irgendwie reichen.

Cipher.getInstance("AES/CBC/NoPadding") funktioniert übrigens schonmal.
Jetzt kriege ich nur eine exception beim byte[] encrypted = cipher.doFinal(input);.

Leider hab ichs gerad vergessen was es genau war.
Werd ich nochmal nachsehen nachher und erfolg (falls ich die ursache finde) bzw misserfolg bekanntgeben.
lunar

sma hat geschrieben:2. Du musst drei Parameter definieren: Verschlüsselungsalgorithmus, Block-Mode, Padding-Algorithmus. Das tust du nicht. Ich vermute daher, dass Java hier andere Defaults hat als Python.
PyCrypto implementiert iirc nur die Algorithmen selbst, um die Aufbereitung der Daten für den gewählten Algorithmus (e.g. Padding) muss sich der Entwickler selbst kümmern.

PyCrypto unterstützt laut API Dokumentation allerdings auch CFB und OFB anstelle von CBC bzw. ECB, so dass man AES wie eine Stromchiffre nutzen kann, was das Padding überflüssig macht.
Mad-Marty
User
Beiträge: 317
Registriert: Mittwoch 18. Januar 2006, 19:46

lunar hat geschrieben:
sma hat geschrieben:2. Du musst drei Parameter definieren: Verschlüsselungsalgorithmus, Block-Mode, Padding-Algorithmus. Das tust du nicht. Ich vermute daher, dass Java hier andere Defaults hat als Python.
PyCrypto implementiert iirc nur die Algorithmen selbst, um die Aufbereitung der Daten für den gewählten Algorithmus (e.g. Padding) muss sich der Entwickler selbst kümmern.

PyCrypto unterstützt laut API Dokumentation allerdings auch CFB und OFB anstelle von CBC bzw. ECB, so dass man AES wie eine Stromchiffre nutzen kann, was das Padding überflüssig macht.
Klingt durchaus verlockend, das AES als Stromchiffre zu benutzen, nur hab ich zweifel, das das mit java dann noch interoperabel ist.
Aber muss ich nochmal nachsehen vielleicht kann es das ja auch.

CFB & OFB .... muss ich auch erstmal nachlesen ob das gut/sicher/schnell ist und welches das beste ist.

Hat jemand vorschläge dazu?
lunar

Mad-Marty hat geschrieben:Klingt durchaus verlockend, das AES als Stromchiffre zu benutzen, nur hab ich zweifel, das das mit java dann noch interoperabel ist.
Beides sind recht bekannte Modi, die zudem keineswegs schwer zu implementieren sind.
CFB & OFB .... muss ich auch erstmal nachlesen ob das gut/sicher/schnell ist und welches das beste ist.

Hat jemand vorschläge dazu?
Der technische Unterschied ist trivial. Bei OFB dient die Ausgabe eines Blocks der Chiffre als Schlüssel für den nächsten Block, bei CFB ist es der resultierende Chiffretext, der als Eingabe für den nächsten Block dient.

Daraus resultierende gewisse Unterschiede im Verhalten: CFB ist selbstsynchronisierend, es ist bei der Entschlüsselung also keine Kenntnis über den internen Zustand der Chiffre zum Zeitpunkt der Verschlüsselung nötig. Das macht dieses Verfahren zum bevorzugten Verfahren für Echtzeit-Netzwerkübertragungen, da man sich die Synchronisation der Zustände sparen kann. Außerdem verhält sich CFB dadurch genau wie CBC: Fehler in Blöcken pflanzen sich fort, ein manipuliertes Bit im Datenstrom zerstört alle folgenden Daten. Das ist schlecht für die Datensicherheit, erschwert aber die Kryptoanalyse.

OFB dagegen ist nicht selbstsynchronisierend. Das ist bei Echtzeit-Übertragungen ein Problem, ansonsten aber ohne Belang, da der Empfänger aus Schlüssel und IV die entsprechende Schlüsselbitfolge rekonstruieren kann. Außerdem pflanzen sich Fehler nicht fort, die Manipulation eines Bits ändert auch nur dieses eine Bit im Klartext. Das erleichtert natürlich die Kryptanalyse, ist aber auch wieder vorteilhaft für die Datensicherheit.

Allerdings sind die Implikationen dieser Wahl möglicherweise weitreichender als vermutet, Sicherheitslücken entstehen selten an Stellen, an denen man sie vermutet hat. Deswegen bleibe ich auch bei meiner Empfehlung, auf keinen Fall ein eigenes Format zu implementieren, sondern ein existierendes, bewährtes Format für dein Projekt zu nutzen.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Mad-Marty hat geschrieben:CFB & OFB .... muss ich auch erstmal nachlesen ob das gut/sicher/schnell ist und welches das beste ist.
Suns SunJCE Provider unterstützt AES mit ECB, CBC, PCBC, CTR, CTS, CFB, CFB...CFB128, OFB, OFB8..OFB128 mit NoPadding, PKCS5Padding, ISO10126Padding. BouncyCastle kann ebenfalls CFB, OFB und vieles mehr.

Stefan
Mad-Marty
User
Beiträge: 317
Registriert: Mittwoch 18. Januar 2006, 19:46

So habe jetzt den CFB mode zum funktionieren gekriegt ...



Allerdings stimmt das ergebnis jetzt nicht mehr zwischen PyCrypto und Java Sun JCE überein, ausser beim 1. byte


Code: Alles auswählen

>>> import binascii
>>> from Crypto.Cipher import AES
>>> IV = '0' * 16
>>> obj=AES.new('abcdefghijklmnop', AES.MODE_CFB, IV)
>>> binascii.hexlify(obj.encrypt('Blah'))
'791b33af'


Java:

Code: Alles auswählen

	       IvParameterSpec iv = new javax.crypto.spec.IvParameterSpec("0000000000000000".getBytes());
	    	 
	       
	       SecretKeySpec key = new SecretKeySpec("abcdefghijklmnop".getBytes("ascii"), "AES");



	       

	       String crypt_mode = "AES/CFB/NoPadding";
	       String encrypt_input = "Blah";

	       Cipher cipher = Cipher.getInstance(crypt_mode);
	       cipher.init(Cipher.ENCRYPT_MODE, key, iv);

	       byte[] encrypted = cipher.doFinal(encrypt_input.getBytes("ascii"));
	       System.out.println(crypt_mode + "encrypted string: " + asHex(encrypted));

	       cipher.init(Cipher.DECRYPT_MODE, key, iv);
	       byte[] original = cipher.doFinal(hexStringToByteArray(asHex(encrypted)));
	       
	       String originalString = new String(original);
	       System.out.println("Original string: " + originalString);
Ausgabe:
AES/CFB/NoPaddingencrypted string: 7957119e
Original string: Blah
Mad-Marty
User
Beiträge: 317
Registriert: Mittwoch 18. Januar 2006, 19:46

Ok das problem ist gelöst.

Java defaultet zum CFB128 modus, während Python den CFB8 modus mit PyCrypto nutzt.

Ein einfaches

Code: Alles auswählen

String crypt_mode = "AES/CFB8/NoPadding";
und die beiden passen zueinander.

Danke für eure hilfe. :-)

Ich konnte im PyCrypto allerdings keine möglichkeit finden, den CFB modus anzugeben (nur zur info), schade das das scheinbar nur den einen kann.

Es gibt 4 CFB modi: CFB1, CFB8, CFB64, CFB128.
Mad-Marty
User
Beiträge: 317
Registriert: Mittwoch 18. Januar 2006, 19:46

Soweit funktioniert alles, eine Frage hab ich aber noch offen ...

Sehe ich das richtig, das die IV (Initialization Vectors) nicht sicher übertragen werden müssen, also die Sicherheit der Chiffre nicht verringern bei bekannt werden?
lunar

Nein, die Klartext-Übertragung des IV hat keinen negativen Einfluss auf die Sicherheit der Chiffre. Der IV wird vor der Verschlüsselung mit dem Klartext verknüpft (CBC) bzw. als initialer Datenblock für die Blockchiffre verwendet (CFB, OFB). Der aus dem IV resultierende Chiffretext ist immer auch abhängig vom Klartext oder vom Schlüssel, so dass mithilfe des IVs keine Known Plaintext Angriffe gegen den Schlüssel möglich sind.
Antworten