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 hilfreicher Beitrag.

Ich muss wohl auch für Java und PHP sowas zu Laufen bekommen und da ist das eine super Basis.

Vielen Dank!!!

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 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

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