AES verschlüsselte Daten zwischen PHP, .net und Java austauschen
In Projekten gibt es immer wieder das Bedürfnis verschlüsselt Daten zwischen Systemen auszutauschen. Dazu bietet sich heute der Standard AES (Advanced Encryption Standard) an.
Um Daten zwischen verschiedenen Plattformen wie PHP, .net oder JAVA auszutauschen, welche AES verschlüsselt sind, liegt die Problematik im Detail, welche wir aber lösen konnten. Damit ihr nicht die selben Probleme lösen müsst, nachfolgend der entsprechende Code.
Der Key und de IV sollten jeweils 16 Zeichen lang sein.
Wenn ihr Anmerkungen oder Verbesserungsvorschläge habt, dann her damit!
PHP
Leider konnten wir nicht 'MCRYPT_RIJNDAEL_256' verwenden da es dann Probleme mit der Länge des Initialization Vector bei C# gibt. Das Notwendige Padding habe ich als zusätzlich Funktion implementiert, da Java ein pkcs5 Padding erwartet.
Nachfolgend die PHP-Klasse zum Ver- und Entschlüsseln von Strings:
<?php
/**
* Class with Encrypt- and Decrypt-Functions
*/
class CryptUtility {
// Data representation
public static $DATA_AS_IS = 0;
public static $DATA_AS_BASE64 = 1;
public static $DATA_AS_HEX = 2;
/**
* Adds pkcs5 padding
* @return Given text with pkcs5 padding
* @param string $data
* String to pad
* @param integer $blocksize
* Blocksize used by encryption
*/
private static function pkcs5Pad($data, $blocksize){
$pad = $blocksize - (strlen($data) % $blocksize);
$returnValue = $data . str_repeat(chr($pad), $pad);
return $returnValue;
}
/**
* Removes padding
* @return Given text with removed padding characters
* @param string $data
* String to unpad
*/
private static function pkcs5Unpad($data) {
$pad = ord($data{strlen($data)-1});
if ($pad > strlen($data)) return false;
if (strspn($data, chr($pad), strlen($data) - $pad) != $pad) return false;
return substr($data, 0, -1 * $pad);
}
/**
* Encrypts a string with the Advanced Encryption Standard.
*
* The used algorythm (cipher) is MCRYPT_RIJNDAEL_128 and the mode is 'cbc' (cipher block chaining).
*
* @return Encrypted text as hexadecimal representation
* @param string $data
* String to encrypt
* @param string $key
* Key
* @param string $iv
* Initialization vector (IV) - 16 char
* @param integer $dataAs [optional]
* Encode data after encryption as (CryptUtility::$DATA_AS_*) - Default CryptUtility::$DATA_AS_IS
*/
public static function aesEncrypt($data, $key, $iv, $dataAs = 0) {
$size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, 'cbc');
$cipher = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', 'cbc', '');
// Add padding to String
$data = CryptUtility::pkcs5Pad($data, $size);
$length = strlen($data);
mcrypt_generic_init($cipher, $key, $iv);
$data = mcrypt_generic($cipher,$data);
if($dataAs == CryptUtility::$DATA_AS_HEX) {
$data = bin2hex($data);
} else if ($dataAs == CryptUtility::$DATA_AS_BASE64) {
$data = base64_encode($data);
}
mcrypt_generic_deinit($cipher);
return $data;
}
/**
* Decrypts a string with the Advanced Encryption Standard.
*
* The used algorythm (cipher) is MCRYPT_RIJNDAEL_128 and the mode is 'cbc' (cipher block chaining).
*
* @return Decrypted text
* @param string $data
* String to decrypt as hexadecimal representation
* @param string $key
* Key
* @param string $iv
* Initialization vector (IV) - 16 char
* @param integer $dataAs [optional]
* Decode data before decryption as (CryptUtility::$DATA_AS_*) - Default CryptUtility::$DATA_AS_IS
*/
public static function aesDecrypt($data, $key, $iv, $dataAs = 0) {
$size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, 'cbc');
$cipher = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', 'cbc', '');
mcrypt_generic_init($cipher, $key, $iv);
if($dataAs == CryptUtility::$DATA_AS_HEX) {
// pack() is used to convert hex string to binary
$data = pack('H*', $data);
} else if ($dataAs == CryptUtility::$DATA_AS_BASE64) {
$data = base64_decode($data);
}
$data = mdecrypt_generic($cipher, $data);
mcrypt_generic_deinit($cipher);
return CryptUtility::pkcs5Unpad($data);
}
}
?>.net - C#
Hier die Implementation in C# zum Ver- und Entschlüsseln welche mit den Strings aus PHP korrespondiert.
Die Klasse:
using System;
using System.Globalization;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace previon.security {
public class CryptoService : ICryptoService {
/// <summary>
/// Verschlüsselt (AES) einen String mittels <paramref name="key"/> und
/// <paramref name="iv"/>.
/// Liefert die Verschlüsselten Daten als Hex String.
/// </summary>
/// <param name="data">Die zu verschlüsselnden Daten.</param>
/// <param name="key">Der Schlüssel.</param>
/// <param name="iv">Der Initialvektor.</param>
/// <returns>Verschlüsselte Daten als Hex String.</returns>
public string Encypt(string data,string key,string iv) {
if (data == null)
throw new ArgumentNullException("data");
if (string.IsNullOrEmpty(key))
throw new ArgumentException("key darf weder null noch empty sein.","key");
if (string.IsNullOrEmpty(iv))
throw new ArgumentException("iv darf weder null noch empty sein.","iv");
string encryptedData = null;
SymmetricAlgorithm algorithm = null;
ICryptoTransform encryptor = null;
MemoryStream encryptedDataStream = null;
CryptoStream cryptoStream = null;
try {
//Algorithmus holen und Encryptor erstellen
algorithm = new RijndaelManaged
{
KeySize = key.Length * 8,
BlockSize = iv.Length * 8,
Key = Encoding.ASCII.GetBytes(key),
IV = Encoding.ASCII.GetBytes(iv),
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7
};
encryptor = algorithm.CreateEncryptor(algorithm.Key,
algorithm.IV);
//Streams und Writer erstellen
encryptedDataStream = new MemoryStream();
cryptoStream = new CryptoStream(encryptedDataStream,encryptor,
CryptoStreamMode.Write);
//Daten direkt über Encoding in Utf8 Transformieren da der StreamWriter die
//Utf8 Präambel den Daten voranstellt
var utf8Data = Encoding.UTF8.GetBytes(data);
cryptoStream.Write(utf8Data,0,utf8Data.Length);
cryptoStream.Flush();
cryptoStream.FlushFinalBlock();
//Verschlüsselte Daten codieren
var encryptedDataBuffer = encryptedDataStream.ToArray();
encryptedData = this.Encode(encryptedDataBuffer);
}
finally {
if (cryptoStream != null)
cryptoStream.Dispose();
if (encryptedDataStream != null)
encryptedDataStream.Dispose();
if (encryptor != null)
encryptor.Dispose();
if (algorithm != null)
algorithm.Clear();
}
return encryptedData;
}
/// <summary>
/// Entschlüsselt (AES) als Hex String vorliegende Daten mittels <paramref name="key"/> und
/// <paramref name="iv"/>.
/// </summary>
/// <param name="data">Verschlüsselte Daten als Hex String.</param>
/// <param name="key">Der Schlüssel.</param>
/// <param name="iv">Der Initialvektor.</param>
/// <returns>Entschlüsselte Daten.</returns>
public string Decrypt(string data,string key,string iv) {
if (string.IsNullOrEmpty(data))
throw new ArgumentException("Das Argument data darf weder null noch empty sein.",
"data");
//Daten decodieren
var decodedData = this.Decode(data);
if (decodedData == null)
throw new ArgumentException("Das Argument data enthält ungültige Daten.",
"data");
string decryptedData = null;
SymmetricAlgorithm algorithm = null;
ICryptoTransform decryptor = null;
MemoryStream encryptedDataStream = null;
CryptoStream cryptoStream = null;
StreamReader cryptoStreamReader = null;
try {
//Algorithmus und Decryptor erstellen
algorithm = new RijndaelManaged
{
KeySize = key.Length * 8,
BlockSize = iv.Length * 8,
Key = Encoding.ASCII.GetBytes(key),
IV = Encoding.ASCII.GetBytes(iv),
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7
};
decryptor = algorithm.CreateDecryptor(algorithm.Key,
algorithm.IV);
//Verschlüsselte Daten in MemoryStream schreiben
encryptedDataStream = new MemoryStream(decodedData);
//Crypto Stream erstellen
cryptoStream = new CryptoStream(encryptedDataStream,decryptor,
CryptoStreamMode.Read);
cryptoStreamReader = new StreamReader(cryptoStream,Encoding.UTF8);
//Daten entschlüsseln
decryptedData = cryptoStreamReader.ReadToEnd();
}
finally {
if (cryptoStreamReader != null)
cryptoStreamReader.Dispose();
if (cryptoStream != null)
cryptoStream.Dispose();
if (encryptedDataStream != null)
encryptedDataStream.Dispose();
if (decryptor != null)
decryptor.Dispose();
if (algorithm != null)
algorithm.Clear();
}
return decryptedData;
}
/// <summary>
/// Encodiert Binärdaten in einen Hex String.
/// </summary>
/// <param name="data">Die Daten.</param>
/// <returns>Hex String der Daten.</returns>
private string Encode(byte[] data) {
var encodeWriter = new StringWriter();
for (int i = 0; i < data.Length; i++) {
encodeWriter.Write("{0:x2}",data[i]);
}
return encodeWriter.ToString();
}
/// <summary>
/// Decodiert einen Hex String in Binärdaten.
/// </summary>
/// <param name="data">Hex String der Daten.</param>
/// <returns>Daten Binär.</returns>
private byte[] Decode(string data) {
var byteCount = data.Length / 2;
var decodedData = new byte[byteCount];
for (int i = 0; i < byteCount; i++) {
var startIndex = i * 2;
decodedData[i] = byte.Parse(data.Substring(startIndex,2),NumberStyles.HexNumber);
}
return decodedData;
}
}
}Das Interface:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace previon.security {
public interface ICryptoService {
/// <summary>
/// Verschlüsselt (AES) einen String mittels <paramref name="key"/> und
/// <paramref name="iv"/>.
/// Liefert die Verschlüsselten Daten als Hex String.
/// </summary>
/// <param name="data">Die zu verschlüsselnden Daten.</param>
/// <param name="key">Der Schlüssel.</param>
/// <param name="iv">Der Initialvektor.</param>
/// <returns>Verschlüsselte Daten als Hex String.</returns>
string Encypt(string data,string key,string iv);
/// <summary>
/// Entschlüsselt (AES) als Hex String vorliegende Daten mittels <paramref name="key"/> und
/// <paramref name="iv"/>.
/// </summary>
/// <param name="data">Verschlüsselte Daten als Hex String.</param>
/// <param name="key">Der Schlüssel.</param>
/// <param name="iv">Der Initialvektor.</param>
/// <returns>Entschlüsselte Daten.</returns>
string Decrypt(string data,string key,string iv);
}
}Java
Und zuletzt noch die Implementation in Java. Wobei ich hier erst den Code zum Entschlüsseln geschrieben habe.
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class AESCrypt {
/**
* HEX to byte conversion
*
* @param HEX
* encoded string
* @return byte array representation of the given string.
*/
private static byte[] hex2byte(String s) {
if (s == null)
return null;
int l = s.length();
if (l % 2 == 1)
return null;
byte[] b = new byte[l / 2];
for (int i = 0; i < l / 2; i++) {
b[i] = (byte) Integer.parseInt(s.substring(i * 2, i * 2 + 2), 16);
}
return b;
}
/**
* @param text
* String to decrypt
* @param key
* Key - 16 char
* @param iv
* Initialization vector (IV) - 16 char
* @return Decrypted text
* @throws GeneralSecurityException
*/
public static String aesDecrypt(String text, String key, String iv)
throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(), "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes());
cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivSpec);
byte[] text2 = hex2byte(text);
byte[] original = cipher.doFinal(text2);
String originalString = null;
try {
originalString = new String(original, "UTF-8"); // Ensure UTF-8
// Encoding
} catch (UnsupportedEncodingException e) {
// UTF-8 is supported
}
return originalString;
}
}
Wie gesagt, Anmerkungen und Verbesserungsvorschläge nehme ich gerne entgegen!



Kommentare
Hallo, super toller und
Hallo,
super toller und hilfreicher Beitrag.
Ich muss wohl auch für Java und PHP sowas zu Laufen bekommen und da ist das eine super Basis.
Vielen Dank!!!
großartige Hilfe
Habe diese CryptUtility-Klasse als Grundlage für die Entschlüsselung von Daten, die auf dem iPhone/iPad encrypted wurden, genommen. Hat nach ein paar kleineren Ergänzungen super geklappt.
Vielen Dank,
Bo
Hey! Wow echt super dein
Hey!
Wow echt super dein Artikel. Hab einen Moment gebraucht, bis ich es in meine Anwendung (php<->c#) implementieren konnte. Aber hat wunderbar geklappt jetzt :)
Vielen, vielen Dank! Dieser Punkt der Sicherheit war sehr kritisch für mein neues Projekt. Durch dich bin ich jetzt quasi schon auf der Zielgeraden :D
Wunderbar, aber Java macht Probleme
Hallo!
Dieser BlogPost hat mir auf den ersten Blick sehr weitergeholfen, da ich eine Verschlüsselungstechnik zwischen C# -> PHP -> Java realisieren muss.
Dank deines Posts habe ich die C# <--> PHP Variante nun umgesetzt.
Aber ich verzweifle nun zwischen der PHP <--> C# Variante.
Testweise verschlüssel ich die Daten mit C#, speicher diese mit PHP allerdings nur ab und gebe sie verschlüsselt an Java weiter.
Leider bekomme ich mit deiner Methode "IllegalBlockSizeExceptions", etc.
Wie muss ich deine pkcs5Pad/pkcs5Unpad Methode arbeiten?
MfG
Sascha
Neuen Kommentar schreiben