Update 5
Switched the question to Security StackExchange - got an answer. https://security.stackexchange.com/questions/30168/aes-cfb-128-decryption-encryption-problem-between-erlang-and-php
Update 4
Having solved the problem or so I thought - I tried to get on with the implementation. Turned out it wasn't quite enough.
Once we started putting real word data in it all went to hell. An example would be increasing the Plain Text from 1234567812345678
to 12345678123456781234567812345678
In this case the first 128-bit block of the cypertext was the same, but the second was different:
PHP:
139 182 94 68 208 173 127 90 14 236 33 230 41 29 210 121 153 57 173 191 237 169 242 222 217 104 116 144 240 175 39 33
Erlang:
<<139,182,94,68,208,173,127,90,14,236,33,230,41,29,210,121,147,172,
114,74,61,11,162,5,112,104,102,63,24,78,34,179>>
Update 3 - (I though it was) SOLVED (but I was wrong)
This answer provided the final clue: Incorrect key size when porting Crypto++ AES encryption to PHP's mcrypt
The problem is that the 'cfb'
mode in mycrypt
doesn't use the right feedback blocksize to emulate aes cfb
- you need to use the mode nofb
.
Update 2
Trying to get AES-CFB-128 to work across both - so I have identified that using mycrypt
with a mode of RINJDAEL_128
with a 128 block size and type 'cfb'
is the PHP equivalent of crypto:aes_cfb_128/3
in Erlang.
So I start to write routines in both languages to prove this.
PHP version:
<?php
// fugly because I don't know enough PHP to write it better
// big apologies to any PHP code poets out there, my bad :(
function dump($String, $Bin) {
echo $String . " is " . ord($Bin[0]) . " " . ord($Bin[1]) . " " . ord($Bin[2]) . " " . ord($Bin[3]) . " " . ord($Bin[4]) . " " . ord($Bin[5]) . " " . ord($Bin[6]) . " " . ord($Bin[7]) . " " . ord($Bin[8]) . " " . ord($Bin[9]) . " " . ord($Bin[10]) . " " . ord($Bin[11]) . " " . ord($Bin[12]) . " " . ord($Bin[13]) . " " . ord($Bin[14]) . " " . ord($Bin[15]) . "\n";
}
$Key = "abcdefghabcdefgh";
$IV = "12345678abcdefgh";
$Text = "1234567812345678";
$KeySize = strlen($Key) * 8;
$IVSize = strlen($IV) * 8;
$TextSize = strlen($Text) * 8;
$Size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CFB);
echo "Block size is " . $Size . " bytes or " . $Size * 8 . " bits\n";
$Crypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $Key, $Text, MCRYPT_MODE_CFB, $IV);
$Decrypt = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $Key, $Crypt, MCRYPT_MODE_CFB, $IV);
echo "Key is " . $Key . " with size " . $KeySize . "\n";
echo "IV is " . $IV . " with size " . $IVSize . "\n";
echo "Text is " . $Text . " with size " . $TextSize . "\n";
echo "Crypt is " . $Crypt . "\n";
dump("Crypt", $Crypt);
echo "Decrypt is " . $Decrypt . "\n";
dump("Decrypt", $Decrypt);
?>
When I run it I get the PHP output of:
Block size is 16 bytes or 128 bits
Key is abcdefghabcdefgh with size 128
IV is 12345678abcdefgh with size 128
Text is 1234567812345678 with size 128
Crypt is ��*�b�ls�M��
Crypt is 139 0 188 42 175 98 18 177 108 27 115 189 77 144 127 176
Decrypt is 1234567812345678
Decrypt is 49 50 51 52 53 54 55 56 49 50 51 52 53 54 55 56
The Erlang version is:
-module(test_crypto).
-export([
test/0
]).
test() ->
Key = <<"abcdefghabcdefgh">>,
IV = <<"12345678abcdefgh">>,
Text = <<"1234567812345678">> ,
KeySize = bit_size(Key),
IVSize = bit_size(IV),
TextSize = bit_size(Text),
io:format("Key is ~p with size ~p~n", [Key, KeySize]),
io:format("IV is ~p with size ~p~n", [IV, IVSize]),
io:format("Text is ~p with size ~p~n", [Text, TextSize]),
Crypt = crypto:aes_cfb_128_encrypt(Key, IV, Text),
io:format("Crypt is ~p~n", [Crypt]),
Decrypt = crypto:aes_cfb_128_decrypt(Key, IV, Crypt),
io:format("Decrypt is ~p~n", [Decrypt]),
ok.
And when I run it I get the Erlang output:
Key is <<"abcdefghabcdefgh">> with size 128
IV is <<"12345678abcdefgh">> with size 128
Text is <<"1234567812345678">> with size 128
Crypt is <<139,182,94,68,208,173,127,90,14,236,33,230,41,29,210,121>>
Decrypt is <<"1234567812345678">>
So each one does the encrypt/decrypt cycle correctly - but the crypto form is different - I can't use it between Erlang and PHP.
I am sure there is some simple explanation - but I am at a loss to understand it.
PHP takes strings as inputs - Erlang takes binaries - but it appears that PHP ascii strings are stored as binaries. Is that right or am I missing soemthing?
Update 1
I have figured out that the crypto:md5_mac/2
function can be replicated with a php function:
function encrypt_term_hex($Key, $Msg) {
return hash_hmac("md5", $Msg, $Key);
}
Original Question
I want to share information between two systems, one written in Erlang and one in PHP.
The plan is to use a system that already runs between multiple Erlang systems that involves signing an Erlang term with a private key which is shared between the Erlang systems.
The Erlang side uses the functions crypto:md5_mac/2
and crypto:aes_cfb_128_encrypt/3
which in turn uses the underlying OpenSSL crypto libraries.
The encryption side (in Erlang) is:
encrypt_bin(Key0, PlainT0) ->
PlainT = extend(PlainT0),
Key = crypto:md5_mac(get_server_salt(), Key0),
crypto:aes_cfb_128_encrypt(Key, get_salt(), PlainT).
What this function does is take a key value pair. It then extends the size of the term to a fixed size, generates a key using md5_mac and a fixed (shared) salt and finally does the encryption - initialising it with another salt vector.
So far so good. My task then is to replicate this fn in PHP (and its decrypting twin, of course).
The Erlang documentation is good:
http://erlang.org/doc/man/crypto.html
Trying to figure out exactly how Erlang calls the underlying Crypto libraries is a bit harder - because the source code shows come obscure NIF macros.
It appears that PHP implements a library wrapper around OpenSSL so it should be straight forward. Unfortunately I can't make head nor tail of it. For example I look at openssl-encrypt and it states it is not documented:
http://php.net/manual/en/function.openssl-encrypt.php
Is there anywhere I can get good examples/documenation of how OpenSSL is implemented in PHP? Or how the Erlang NIF loader works? Or both?