Encrypt and Decrypt text with RSA in PHP - php

Is there any class for PHP 5.3 that provides RSA encryption/decryption without padding?
I've got private and public key, p,q, and modulus.

You can use phpseclib, a pure PHP RSA implementation:
<?php
include('Crypt/RSA.php');
$privatekey = file_get_contents('private.key');
$rsa = new Crypt_RSA();
$rsa->loadKey($privatekey);
$plaintext = new Math_BigInteger('aaaaaa');
echo $rsa->_exponentiate($plaintext)->toBytes();
?>

Security warning: This code snippet is vulnerable to Bleichenbacher's 1998 padding oracle attack. See this answer for better security.
class MyEncryption
{
public $pubkey = '...public key here...';
public $privkey = '...private key here...';
public function encrypt($data)
{
if (openssl_public_encrypt($data, $encrypted, $this->pubkey))
$data = base64_encode($encrypted);
else
throw new Exception('Unable to encrypt data. Perhaps it is bigger than the key size?');
return $data;
}
public function decrypt($data)
{
if (openssl_private_decrypt(base64_decode($data), $decrypted, $this->privkey))
$data = $decrypted;
else
$data = '';
return $data;
}
}

No application written in 2017 (or thereafter) that intends to incorporate serious cryptography should use RSA any more. There are better options for PHP public-key cryptography.
There are two big mistakes that people make when they decide to encrypt with RSA:
Developers choose the wrong padding mode.
Since RSA cannot, by itself, encrypt very long strings, developers will often break a string into small chunks and encrypt each chunk independently. Sort of like ECB mode.
The Best Alternative: sodium_crypto_box_seal() (libsodium)
$keypair = sodium_crypto_box_keypair();
$publicKey = sodium_crypto_box_publickey($keypair);
// ...
$encrypted = sodium_crypto_box_seal(
$plaintextMessage,
$publicKey
);
// ...
$decrypted = sodium_crypto_box_seal_open(
$encrypted,
$keypair
);
Simple and secure. Libsodium will be available in PHP 7.2, or through PECL for earlier versions of PHP. If you need a pure-PHP polyfill, get paragonie/sodium_compat.
Begrudgingly: Using RSA Properly
The only reason to use RSA in 2017 is, "I'm forbidden to install PECL extensions and therefore cannot use libsodium, and for some reason cannot use paragonie/sodium_compat either."
Your protocol should look something like this:
Generate a random AES key.
Encrypt your plaintext message with the AES key, using an AEAD encryption mode or, failing that, CBC then HMAC-SHA256.
Encrypt your AES key (step 1) with your RSA public key, using RSAES-OAEP + MGF1-SHA256
Concatenate your RSA-encrypted AES key (step 3) and AES-encrypted message (step 2).
Instead of implementing this yourself, check out EasyRSA.
Further reading: Doing RSA in PHP correctly.

If you are using PHP >= 7.2 consider using inbuilt sodium core extension for encrption.
It is modern and more secure. You can find more information here - http://php.net/manual/en/intro.sodium.php. and here - https://paragonie.com/book/pecl-libsodium/read/00-intro.md
Example PHP 7.2 sodium encryption class -
<?php
/**
* Simple sodium crypto class for PHP >= 7.2
* #author MRK
*/
class crypto {
/**
*
* #return type
*/
static public function create_encryption_key() {
return base64_encode(sodium_crypto_secretbox_keygen());
}
/**
* Encrypt a message
*
* #param string $message - message to encrypt
* #param string $key - encryption key created using create_encryption_key()
* #return string
*/
static function encrypt($message, $key) {
$key_decoded = base64_decode($key);
$nonce = random_bytes(
SODIUM_CRYPTO_SECRETBOX_NONCEBYTES
);
$cipher = base64_encode(
$nonce .
sodium_crypto_secretbox(
$message, $nonce, $key_decoded
)
);
sodium_memzero($message);
sodium_memzero($key_decoded);
return $cipher;
}
/**
* Decrypt a message
* #param string $encrypted - message encrypted with safeEncrypt()
* #param string $key - key used for encryption
* #return string
*/
static function decrypt($encrypted, $key) {
$decoded = base64_decode($encrypted);
$key_decoded = base64_decode($key);
if ($decoded === false) {
throw new Exception('Decryption error : the encoding failed');
}
if (mb_strlen($decoded, '8bit') < (SODIUM_CRYPTO_SECRETBOX_NONCEBYTES + SODIUM_CRYPTO_SECRETBOX_MACBYTES)) {
throw new Exception('Decryption error : the message was truncated');
}
$nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
$ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
$plain = sodium_crypto_secretbox_open(
$ciphertext, $nonce, $key_decoded
);
if ($plain === false) {
throw new Exception('Decryption error : the message was tampered with in transit');
}
sodium_memzero($ciphertext);
sodium_memzero($key_decoded);
return $plain;
}
}
Sample Usage -
<?php
$key = crypto::create_encryption_key();
$string = 'Sri Lanka is a beautiful country !';
echo $enc = crypto::encrypt($string, $key);
echo crypto::decrypt($enc, $key);

Yes. Look at http://jerrywickey.com/test/testJerrysLibrary.php
It gives sample code examples for RSA encryption and decryption in PHP as well as RSA encryption in javascript.
If you want to encrypt text instead of just base 10 numbers, you'll also need a base to base conversion. That is convert text to a very large number. Text is really just writing in base 63. 26 lowercase letters plus 26 uppercase + 10 numerals + space character. The code for that is below also.
The $GETn parameter is a file name that holds keys for the cryption functions. If you don't figure it out, ask. I'll help.
I actually posted this whole encryption library yesterday, but Brad Larson a mod, killed it and said this kind of stuff isn't really what Stack Overflow is about. But you can still find all the code examples and the whole function library to carry out client/server encryption decryption for AJAX at the link above.
function RSAencrypt( $num, $GETn){
if ( file_exists( 'temp/bigprimes'.hash( 'sha256', $GETn).'.php')){
$t= explode( '>,', file_get_contents('temp/bigprimes'.hash( 'sha256', $GETn).'.php'));
return JL_powmod( $num, $t[4], $t[10]);
}else{
return false;
}
}
function RSAdecrypt( $num, $GETn){
if ( file_exists( 'temp/bigprimes'.hash( 'sha256', $GETn).'.php')){
$t= explode( '>,', file_get_contents('temp/bigprimes'.hash( 'sha256', $GETn).'.php'));
return JL_powmod( $num, $t[8], $t[10]);
}else{
return false;
}
}
function JL_powmod( $num, $pow, $mod) {
if ( function_exists('bcpowmod')) {
return bcpowmod( $num, $pow, $mod);
}
$result= '1';
do {
if ( !bccomp( bcmod( $pow, '2'), '1')) {
$result = bcmod( bcmul( $result, $num), $mod);
}
$num = bcmod( bcpow( $num, '2'), $mod);
$pow = bcdiv( $pow, '2');
} while ( bccomp( $pow, '0'));
return $result;
}
function baseToBase ($message, $fromBase, $toBase){
$from= strlen( $fromBase);
$b[$from]= $fromBase;
$to= strlen( $toBase);
$b[$to]= $toBase;
$result= substr( $b[$to], 0, 1);
$f= substr( $b[$to], 1, 1);
$tf= digit( $from, $b[$to]);
for ($i=strlen($message)-1; $i>=0; $i--){
$result= badd( $result, bmul( digit( strpos( $b[$from], substr( $message, $i, 1)), $b[$to]), $f, $b[$to]), $b[$to]);
$f= bmul($f, $tf, $b[$to]);
}
return $result;
}
function digit( $from, $bto){
$to= strlen( $bto);
$b[$to]= $bto;
$t[0]= intval( $from);
$i= 0;
while ( $t[$i] >= intval( $to)){
if ( !isset( $t[$i+1])){
$t[$i+1]= 0;
}
while ( $t[$i] >= intval( $to)){
$t[$i]= $t[$i] - intval( $to);
$t[$i+1]++;
}
$i++;
}
$res= '';
for ( $i=count( $t)-1; $i>=0; $i--){
$res.= substr( $b[$to], $t[$i], 1);
}
return $res;
}
function badd( $n1, $n2, $nbase){
$base= strlen( $nbase);
$b[$base]= $nbase;
while ( strlen( $n1) < strlen( $n2)){
$n1= substr( $b[$base], 0, 1) . $n1;
}
while ( strlen( $n1) > strlen( $n2)){
$n2= substr( $b[$base], 0, 1) . $n2;
}
$n1= substr( $b[$base], 0, 1) . $n1;
$n2= substr( $b[$base], 0, 1) . $n2;
$m1= array();
for ( $i=0; $i<strlen( $n1); $i++){
$m1[$i]= strpos( $b[$base], substr( $n1, (strlen( $n1)-$i-1), 1));
}
$res= array();
$m2= array();
for ($i=0; $i<strlen( $n1); $i++){
$m2[$i]= strpos( $b[$base], substr( $n2, (strlen( $n1)-$i-1), 1));
$res[$i]= 0;
}
for ($i=0; $i<strlen( $n1) ; $i++){
$res[$i]= $m1[$i] + $m2[$i] + $res[$i];
if ($res[$i] >= $base){
$res[$i]= $res[$i] - $base;
$res[$i+1]++;
}
}
$o= '';
for ($i=0; $i<strlen( $n1); $i++){
$o= substr( $b[$base], $res[$i], 1).$o;
}
$t= false;
$o= '';
for ($i=strlen( $n1)-1; $i>=0; $i--){
if ($res[$i] > 0 || $t){
$o.= substr( $b[$base], $res[$i], 1);
$t= true;
}
}
return $o;
}
function bmul( $n1, $n2, $nbase){
$base= strlen( $nbase);
$b[$base]= $nbase;
$m1= array();
for ($i=0; $i<strlen( $n1); $i++){
$m1[$i]= strpos( $b[$base], substr($n1, (strlen( $n1)-$i-1), 1));
}
$m2= array();
for ($i=0; $i<strlen( $n2); $i++){
$m2[$i]= strpos( $b[$base], substr($n2, (strlen( $n2)-$i-1), 1));
}
$res= array();
for ($i=0; $i<strlen( $n1)+strlen( $n2)+2; $i++){
$res[$i]= 0;
}
for ($i=0; $i<strlen( $n1) ; $i++){
for ($j=0; $j<strlen( $n2) ; $j++){
$res[$i+$j]= ($m1[$i] * $m2[$j]) + $res[$i+$j];
while ( $res[$i+$j] >= $base){
$res[$i+$j]= $res[$i+$j] - $base;
$res[$i+$j+1]++;
}
}
}
$t= false;
$o= '';
for ($i=count( $res)-1; $i>=0; $i--){
if ($res[$i]>0 || $t){
$o.= substr( $b[$base], $res[$i], 1);
$t= true;
}
}
return $o;
}

I have difficulty in decrypting a long string that is encrypted in python. Here is the python encryption function:
def RSA_encrypt(public_key, msg, chunk_size=214):
"""
Encrypt the message by the provided RSA public key.
:param public_key: RSA public key in PEM format.
:type public_key: binary
:param msg: message that to be encrypted
:type msg: string
:param chunk_size: the chunk size used for PKCS1_OAEP decryption, it is determined by \
the private key length used in bytes - 42 bytes.
:type chunk_size: int
:return: Base 64 encryption of the encrypted message
:rtype: binray
"""
rsa_key = RSA.importKey(public_key)
rsa_key = PKCS1_OAEP.new(rsa_key)
encrypted = b''
offset = 0
end_loop = False
while not end_loop:
chunk = msg[offset:offset + chunk_size]
if len(chunk) % chunk_size != 0:
chunk += " " * (chunk_size - len(chunk))
end_loop = True
encrypted += rsa_key.encrypt(chunk.encode())
offset += chunk_size
return base64.b64encode(encrypted)
The decryption in PHP:
/**
* #param base64_encoded string holds the encrypted message.
* #param Resource your private key loaded using openssl_pkey_get_private
* #param integer Chunking by bytes to feed to the decryptor algorithm.
* #return String decrypted message.
*/
public function RSADecyrpt($encrypted_msg, $ppk, $chunk_size=256){
if(is_null($ppk))
throw new Exception("Returned message is encrypted while you did not provide private key!");
$encrypted_msg = base64_decode($encrypted_msg);
$offset = 0;
$chunk_size = 256;
$decrypted = "";
while($offset < strlen($encrypted_msg)){
$decrypted_chunk = "";
$chunk = substr($encrypted_msg, $offset, $chunk_size);
if(openssl_private_decrypt($chunk, $decrypted_chunk, $ppk, OPENSSL_PKCS1_OAEP_PADDING))
$decrypted .= $decrypted_chunk;
else
throw new exception("Problem decrypting the message");
$offset += $chunk_size;
}
return $decrypted;
}

Related

Decrypting string encrypted in C# with RijndaelManaged class using PHP

Here's some C# code (I've modified it slightly to modify some of the hard coded values in it):
public static string Decrypt(string InputFile)
{
string outstr = null;
if ((InputFile != null))
{
if (File.Exists(InputFile))
{
FileStream fsIn = null;
CryptoStream cstream = null;
try
{
byte[] _b = { 94, 120, 102, 204, 199, 246, 243, 104, 185, 115, 76, 48, 220, 182, 112, 101 };
fsIn = File.Open(InputFile, FileMode.Open, System.IO.FileAccess.Read);
SymmetricAlgorithm symm = new RijndaelManaged();
PasswordDeriveBytes Key = new PasswordDeriveBytes(System.Environment.MachineName, System.Text.Encoding.Default.GetBytes("G:MFX62rlABW:IUYAX(i"));
ICryptoTransform transform = symm.CreateDecryptor(Key.GetBytes(24), _b);
cstream = new CryptoStream(fsIn, transform, CryptoStreamMode.Read);
StreamReader sr = new StreamReader(cstream);
char[] buff = new char[1000];
sr.Read(buff, 0, 1000);
outstr = new string(buff);
}
finally
{
if (cstream != null)
{
cstream.Close();
}
if (fsIn != null)
{
fsIn.Close();
}
}
}
}
return outstr;
}
I need to come up with a function to do the same in PHP. Bear in mind, I did not write the C# code and I cannot modify it, so even if it's bad, I'm stuck with it. I've searched all over and have found bits and pieces around, but nothing that works so far. All examples I've found use mcrypt, which seems to be frowned upon these days, but I'm probably stuck using it. Next, I found the following post which has some useful info: Rewrite Rijndael 256 C# Encryption Code in PHP
So looks like the PasswordDeriveBytes class is the key to this. I created the following PHP code to try to decrypt:
function PBKDF1($pass,$salt,$count,$dklen) {
$t = $pass.$salt;
//echo 'S||P: '.bin2hex($t).'<br/>';
$t = sha1($t, true);
//echo 'T1:' . bin2hex($t) . '<br/>';
for($i=2; $i <= $count; $i++) {
$t = sha1($t, true);
//echo 'T'.$i.':' . bin2hex($t) . '<br/>';
}
$t = substr($t,0,$dklen);
return $t;
}
$input = 'Ry5WdjGS8rpA9eA+iQ3aPw==';
$key = "win7x64";
$salt = implode(unpack('C*', "G:MFX62rlABW:IUYAX(i"));
$salt = pack("H*", $salt);
$it = 1000;
$keyLen = 16;
$key = PBKDF1($key, $salt, $it, $keyLen);
$key = bin2hex(substr($key, 0, 8));
$iv = bin2hex(substr($key, 8, 8));
echo trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, base64_decode($input), MCRYPT_MODE_CBC, $iv));
You'll note that for what I believe to be System.Environment.MachineName, I put in a fixed value for now which is the computer name of the machine I'm on, so should be equivalent of what the C# code is doing. Other than that, I've noticed that using MCRYPT_RIJNDAEL_256 doesn't work, it throws the error "The IV parameter must be as long as the blocksize". If I use MCRYPT_RIJNDAEL_128, I don't get that error, but decryption still fails. I assume I'm missing the piece for the byte array _b that's used by the CreateDecryptor function, I have no idea where that's supposed to fit in. Any help is appreciated.
UPDATE
This is the solution, which was made possible by the answer marked correct. Note that the code for the PBKDF1 function is not mine, it was linked to in the answer.
function PBKDF1($pass, $salt, $count, $cb) {
static $base;
static $extra;
static $extracount= 0;
static $hashno;
static $state = 0;
if ($state == 0)
{
$hashno = 0;
$state = 1;
$key = $pass . $salt;
$base = sha1($key, true);
for($i = 2; $i < $count; $i++)
{
$base = sha1($base, true);
}
}
$result = "";
if ($extracount > 0)
{
$rlen = strlen($extra) - $extracount;
if ($rlen >= $cb)
{
$result = substr($extra, $extracount, $cb);
if ($rlen > $cb)
{
$extracount += $cb;
}
else
{
$extra = null;
$extracount = 0;
}
return $result;
}
$result = substr($extra, $rlen, $rlen);
}
$current = "";
$clen = 0;
$remain = $cb - strlen($result);
while ($remain > $clen)
{
if ($hashno == 0)
{
$current = sha1($base, true);
}
else if ($hashno < 1000)
{
$n = sprintf("%d", $hashno);
$tmp = $n . $base;
$current .= sha1($tmp, true);
}
$hashno++;
$clen = strlen($current);
}
// $current now holds at least as many bytes as we need
$result .= substr($current, 0, $remain);
// Save any left over bytes for any future requests
if ($clen > $remain)
{
$extra = $current;
$extracount = $remain;
}
return $result;
}
$input = 'base 64 encoded string to decrypt here';
$key = strtoupper(gethostname());
$salt = 'G:MFX62rlABW:IUYAX(i';
$it = 100;
$keyLen = 24;
$key = PBKDF1($key, $salt, $it, $keyLen);
$iv = implode(array_map('chr', [94, 120, 102, 204, 199, 246, 243, 104, 185, 115, 76, 48, 220, 182, 112, 101]));
_b is a static value that is used as the IV (CreateDecryptor takes a key and IV parameter). Since it is 16 bytes long, this means that you're using Rijndael-128 or more commonly known AES.
Key.GetBytes(24) suggests that a 24 byte key is derived and not a 16 byte key.
Make sure that
System.Text.Encoding.Default is equivalent with implode(unpack('C*', ...,
Default value for iterations of PasswordDeriveBytes is 1000,
Default value for hash of PasswordDeriveBytes is SHA-1
Security problems:
PBKDF1 is obsolete and PBKDF2 isn't that much better. Use up-to-date key derivation algorithms like Argon2 or scrypt.
The IV must be randomly chosen to achieve semantic security. It doesn't have to be secret, so it can be sent along with the ciphertext.
Stretching a key by encoding it to hex doesn't provide any security (don't use bin2hex).
The ciphertext is not authenticated, which means that you cannot detect (malicious) manipulation of encrypted messages. Employ encrypt-then-MAC.

3DES CB Encryption Dynamic Key

A guy told me to encrypt data by using a dynamic key generated according to the following algorithm
timestamp = '080717032817'
static_key = A270AE59FF7782A2EDFE1A781BAB888D0B749D265865C288
k1 = first 5 bytes of the static key
k2 = first 3 bytes of the integer value of the timestamp
k3 = last 5 bytes of the static key
k4 = last 5 bytes of the timestamp
dynamic_key = k1k2k3k4
He told be that the data should be padded :
pad to reach a multiple of the blocksize (8byte for 3DES CBC),
or
pad with 8 null bytes if the length of the data is already multiple of 8.
In his example, with an iv='0123456789ABCDEF' he obtains:
<DATA> <TIMESTAMP> <BASE64 CRYPTED DATA>
3408682266,080717032817,hkUIYwssDQCq0GLx/5MiZg==
To implement the algo, I wrote this class functions
private function __encrypt($data,$parameters,$time,$convert = false)
{
$staticKey =
$mode = MCRYPT_MODE_CBC;
$ivi = '0123456789ABCDEF';
$cipher = MCRYPT_3DES;
$this->log[] = "Encrypt params: mode => {$mode}, cipher => {$cipher}, iv =>{$ivi}";
$dynamicKey =
$iv = pack('H*', $ivi);
$this->log[] = 'Initial Vector '. var_dump($iv);
$data = $this->__padder($data,mcrypt_get_block_size($cipher, $mode),$convert);
$this->log[] = ('Data After padding: ' . $data .", length (bytes):" . strlen($data)/2);
try {
$output = mcrypt_encrypt($cipher, $dynamicKey, $data, $mode,$iv);
} catch (Exception $ex) {
debug($ex->getMessage());
throw new Exception($ex->getMessage());
}
return $output;
}
/**
* Generate a dynamic key based on a timestamp and a static key
* #param type $static_key
*/
private function __generateDynamicKey($static_key = '', $time)
{
$dateObj = DateTime::createFromFormat("ymdHis", $time);
$k[1] = substr($static_key,0, 10);
$k[2] = substr($time,0,6);debug($k[2]);
$k[3] = substr($static_key,strlen($static_key) - 10, 10);
$k[4] = substr($time,strlen($time)-6,6);debug($k[4]); //last 3 bytes
$this->log[] = ("Dynamic key =>".join("",$k). ', length in bytes=>'. strlen(pack('H*', join("",$k))));
return pack('H*', join("",$k));
}
/**
*
* #param type $data
* #param type $blockSize
* #param type $convert
* #return string
*/
private function __padder($data,$blockSize = 8,$convert = false)
{
if ($convert)
$data = Generic::strToHex($data); // hex representation of the data
$this->log[] = 'Block size of cipher: ' .$blockSize;
$this->log[] = ("Hex value before padding=>".($data) );
//Chek if the data is padded to 16 bytes
$dataBytes = strlen($data) / 2 ; // 1 byte = 2 Hex digits
$this->log[] = "Data num. of bytes " . $dataBytes;
$rest = $dataBytes % $blockSize; // The num of bytes is a multiple of blockSize ?
$nearest = ceil($dataBytes/$blockSize ) * $blockSize;
$output = $data;
if ($rest != 0)
{
$delta = ($nearest - $dataBytes); // in bytes
$deltaValue = Generic::zeropad($delta, 2);
$this->log[] = ('padding value '.$deltaValue);
}
else
{
$this->log[] = ('Add 8 bytes of padding!');
$delta = 8;
$deltaValue = '00';
}
$output = $data . str_repeat($deltaValue, $delta);
$this->log[] = ('Hex value after padding '. $output . ', length in bytes =>' . strlen($output)/2);
return $output;
}
public function test($clearUserCode)
{
$userCode = $this->__encrypt($clearUserCode,$provider,$time,true); //UserCode is given as string, mut be converted in hex
$this->log[] = ('UserCode Clear : ' . $clearUserCode . ', in hex: ' . Generic::strToHex($clearUserCode));
$this->log[] = ('UserCode Crypt : ' . bin2hex($userCode));
$this->log[] = ('UserCode Crypt and base64: ' . base64_encode(($userCode)));
$this->log[] = ('----------------------End encrypt UserCode part----------------------') ;
}
And finally somewhere
$this->test('3408682266');
which give to me a different result:
UserCode Clear : 3408682266, in hex: 33343038363832323636
UserCode Crypt : 9d7e195a8d85aa7d051362dfae0042c2
UserCode Crypt and base64: nX4ZWo2Fqn0FE2LfrgBCwg==
Any hint?
After googling somewhere, I discovered that 3DES wants a 192 bits key, and in some way php's mcrypt does not do that magically: You have to pad the key yourself! These are the two functions that work for the examples of the question:
/**
* Make ciphering (3DES) in pkc7 (padding with 0 or a filling value)
* key must bey 24 bytes (192 bits)
* #param type $key
* #param type $iv
* #param type $text
* #return type
*/
public static function encryptNET3DES($key,$iv,$text)
{
$td = mcrypt_module_open(MCRYPT_3DES, '', MCRYPT_MODE_CBC, '');
// Complete the key
$key_add = 24-strlen($key);
$key .= substr($key,0,$key_add);
// Padding the text
$block = mcrypt_get_block_size("tripledes", "cbc");
$len = strlen($text);
$padding = $block - ($len % $block);
$text .= str_repeat(chr($padding),$padding);
mcrypt_generic_init ($td, $key, $iv);
$encrypt_text = mcrypt_generic ($td, $text);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
return $encrypt_text;
}
/**
* Decrypt 3DES encrypted data with 24-bit key
* #param type $key
* #param type $iv
* #param type $text
* #return type
*/
public static function decryptNET3DES($key,$iv,$text)
{
$td = mcrypt_module_open (MCRYPT_3DES, "", MCRYPT_MODE_CBC, "");
// Complete the key
$key_add = 24-strlen($key);
$key .= substr($key,0,$key_add);
mcrypt_generic_init ($td, $key, $iv);
$decrypt_text = mdecrypt_generic ($td, $text);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
//remove the padding text
$block = mcrypt_get_block_size("tripledes", "cbc");
$packing = ord($decrypt_text{strlen($decrypt_text) - 1});
if($packing && ($packing < $block))
// Get rid of padded data
{
for($P = strlen($decrypt_text) - 1; $P >= strlen($decrypt_text) - $packing; $P--) {
if(ord($decrypt_text{$P}) != $packing)
{
$packing = 0;
}
}
}
$decrypt_text = substr($decrypt_text,0,strlen($decrypt_text) - $packing);
return $decrypt_text;
}

Warning: mcrypt_generic_init(): Iv size is incorrect

I am trying to make a PHP AES decrypter, but it will not work.
Right now I have this code:
<?php
require_once('padCrypt.php');
require_once('AES_Encryption.php');
$key = "1234567890123456";
$iv = "";
$padding = "ZERO";
$mode = "ecb";
$message = "dlQO04ftNjSgj/XNdLmz29MtqB1wK15/5E9wpfIenvWkkYXkK/BMCuUKjIyPUvcUFVJpxyJDit6EiiO4l0KifXu/9Y4LB26OzfV4DRsTWL1civllo07Wicw7tlQiUcmu";
$AES = new AES_Encryption($key, $iv, $padding, $mode);
$decrypted = $AES->decrypt($message);
echo $decrypted;
?>
Which returns this error:
Warning: mcrypt_generic_init() [function.mcrypt-generic-init]: Iv size
incorrect; supplied length: 0, needed: 16 in
/var/www/krellers.dk/public_html/tester/AES_Encryption.php on line 152
Could someone help me to understand why it comes with this error evenhough ECB is specific about iv should be empty string, null etc.
I have used the example and library from: http://www.coderelic.com/2011/10/aes-256-encryption-with-php/
AES_Encryption.php file:
<?php
/***
* AES_Encryption
* This class allows you to easily encrypt and decrypt text in AES format
* The class automatically determines whether you need 128, 192, or 256 bits
* based on your key size. It handles multiple padding formats.
*
* Dependencies:
* This class is dependent on PHP's mcrypt extension and a class called padCrypt
*
* Information about mcrypt extension is at:
* http://us.php.net/mcrypt
*
* padCrypt class is published at:
* http://dev.strategystar.net/2011/10/php-cryptography-padding-ansi-x-923-iso-10126-pkcs7-bit-zero/
*
* The padCrypt class provides methods for padding strings with the
* common methods described at:
* http://en.wikipedia.org/wiki/Padding_%28cryptography%29
*
* -- AES_Encryption Information
*
* Key Sizes:
* 16 bytes = 128 bit encryption
* 24 bytes = 192 bit encryption
* 32 bytes = 256 bit encryption
*
* Padding Formats:
* ANSI_X.923
* ISO_10126
* PKCS7
* BIT
* ZERO
*
* The default padding method in this AES_Encryption class is ZERO padding
* ZERO padding is generally OK for paddings in messages because
* null bytes stripped at the end of a readable message should not hurt
* the point of the text. If you are concerned about message integrity,
* you can use PKCS7 instead
*
* This class does not generate keys or vectors for you. You have to
* generate them yourself because you need to keep track of them yourself
* anyway in order to decrypt AES encryptions.
*
* -- Example Usage:
*
* $key = "bac09c63f34c9845c707228b20cac5e0";
* $iv = "47c743d1b21de03034e0842352ae6b98";
* $message = "Meet me at 11 o'clock behind the monument.";
*
* $AES = new AES_Encryption($key, $iv);
* $encrypted = $AES->encrypt($message);
* $decrypted = $AES->decrypt($encrypted);
* $base64_encrypted = base64_encode($encrypted);
*
* -- Credits:
*
* #author Strategy Star Inc.
* #website http://www.strategystar.net
**/
class AES_Encryption
{
private $key, $initVector, $mode, $cipher, $encryption = null;
private $allowed_bits = array(128, 192, 256);
private $allowed_modes = array('ecb', 'cfb', 'cbc', 'nofb', 'ofb');
private $vector_modes = array('cbc','cfb','ofb');
private $allowed_paddings = array(
'ANSI_X.923' => 'ANSI_X923',
'ISO_10126' => 'ISO_10126',
'PKCS7' => 'PKCS7',
'BIT' => 'BIT',
'ZERO' => 'ZERO',
);
private $padCrypt_url = 'http://dev.strategystar.net/2011/10/php-cryptography-padding-ansi-x-923-iso-10126-pkcs7-bit-zero/';
private $aesEncrypt_url = 'http://dev.strategystar.net/';
/***
* String $key = Your secret key that you will use to encrypt/decrypt
* String $initVector = Your secret vector that you will use to encrypt/decrypt if using CBC, CFB, OFB, or a STREAM algorhitm that requires an IV
* String $padding = The padding method you want to use. The default is ZERO (aka NULL byte) [ANSI_X.923,ISO_10126,PKCS7,BIT,ZERO]
* String $mode = The encryption mode you want to use. The default is cbc [ecb,cfb,cbc,stream,nofb,ofb]
**/
public function __construct($key, $initVector='', $padding='ZERO', $mode='cbc')
{
$mode = strtolower($mode);
$padding = strtoupper($padding);
if(!class_exists('padCrypt'))
{
throw new Exception('The padCrypt class must be loaded for AES_Encryption to work: '.$padCrypt_url);
}
if(!function_exists('mcrypt_module_open'))
{
throw new Exception('The mcrypt extension must be loaded.');
}
if(strlen($initVector) != 16 && in_array($mode, $this->vector_modes))
{
throw new Exception('The $initVector is supposed to be 16 bytes in for CBC, CFB, NOFB, and OFB modes.');
}
elseif(!in_array($mode, $this->vector_modes) && !empty($initVector))
{
throw new Exception('The specified encryption mode does not use an initialization vector. You should pass an empty string, zero, FALSE, or NULL.');
}
$this->encryption = strlen($key)*8;
if(!in_array($this->encryption, $this->allowed_bits))
{
throw new Exception('The $key must be either 16, 24, or 32 bytes in length for 128, 192, and 256 bit encryption respectively.');
}
$this->key = $key;
$this->initVector = $initVector;
if(!in_array($mode, $this->allowed_modes))
{
throw new Exception('The $mode must be one of the following: '.implode(', ', $this->allowed_modes));
}
if(!array_key_exists($padding, $this->allowed_paddings))
{
throw new Exception('The $padding must be one of the following: '.implode(', ', $this->allowed_paddings));
}
$this->mode = $mode;
$this->padding = $padding;
$this->cipher = mcrypt_module_open('rijndael-128', '', $this->mode, '');
$this->block_size = mcrypt_get_block_size('rijndael-128', $this->mode);
}
/***
* String $text = The text that you want to encrypt
**/
public function encrypt($text)
{
mcrypt_generic_init($this->cipher, $this->key, $this->initVector);
$encrypted_text = mcrypt_generic($this->cipher, $this->pad($text, $this->block_size));
mcrypt_generic_deinit($this->cipher);
return $encrypted_text;
}
/***
* String $text = The text that you want to decrypt
**/
public function decrypt($text)
{
mcrypt_generic_init($this->cipher, $this->key, $this->initVector);
$decrypted_text = mdecrypt_generic($this->cipher, $text);
mcrypt_generic_deinit($this->cipher);
return $this->unpad($decrypted_text);
}
/***
* Use this function to export the key, init_vector, padding, and mode
* This information is necessary to later decrypt an encrypted message
**/
public function getConfiguration()
{
return array(
'key' => $this->key,
'init_vector' => $this->initVector,
'padding' => $this->padding,
'mode' => $this->mode,
'encryption' => $this->encryption . ' Bit',
'block_size' => $this->block_size,
);
}
private function pad($text, $block_size)
{
return call_user_func_array(array('padCrypt', 'pad_'.$this->allowed_paddings[$this->padding]), array($text, $block_size));
}
private function unpad($text)
{
return call_user_func_array(array('padCrypt', 'unpad_'.$this->allowed_paddings[$this->padding]), array($text));
}
public function __destruct()
{
mcrypt_module_close($this->cipher);
}
}
padCrypt.php file:
<?php
/**
* padCrypt.php
*
* This class can be used to pad strings with the following methods:
* ANSI X.923, ISO 10126, PKCS7, Zero Padding, and Bit Padding
*
* The methods are implemented as documented at:
* http://en.wikipedia.org/wiki/Padding_(cryptography)
*
* #author Strategy Star Inc.
* #website http://www.strategystar.net
*/
class padCrypt
{
public static function pad_ISO_10126($data, $block_size)
{
$padding = $block_size - (strlen($data) % $block_size);
for($x=1; $x<$padding; $x++)
{
mt_srand();
$data .= chr(mt_rand(0,255));
}
return $data . chr($padding);
}
public static function unpad_ISO_10126($data)
{
$length = ord(substr($data, -1));
return substr($data, 0, strlen($data)-$length);
}
public static function pad_ANSI_X923($data, $block_size)
{
$padding = $block_size - (strlen($data) % $block_size);
return $data . str_repeat(chr(0), $padding - 1) . chr($padding);
}
public static function unpad_ANSI_X923($data)
{
$length = ord(substr($data, -1));
$padding_position = strlen($data) - $length;
$padding = substr($data, $padding_position, -1);
for($x=0; $x<$length; $x++)
{
if(ord(substr($padding, $x, 1)) != 0)
{
return $data;
}
}
return substr($data, 0, $padding_position);
}
public static function pad_PKCS7($data, $block_size)
{
$padding = $block_size - (strlen($data) % $block_size);
$pattern = chr($padding);
return $data . str_repeat($pattern, $padding);
}
public static function unpad_PKCS7($data)
{
$pattern = substr($data, -1);
$length = ord($pattern);
$padding = str_repeat($pattern, $length);
$pattern_pos = strlen($data) - $length;
if(substr($data, $pattern_pos) == $padding)
{
return substr($data, 0, $pattern_pos);
}
return $data;
}
public static function pad_BIT($data, $block_size)
{
$length = $block_size - (strlen($data) % $block_size) - 1;
return $data . "\x80" . str_repeat("\x00", $length);
}
public static function unpad_BIT($data)
{
if(substr(rtrim($data, "\x00"), -1) == "\x80")
{
return substr(rtrim($data, "\x00"), 0, -1);
}
return $data;
}
public static function pad_ZERO($data, $block_size)
{
$length = $block_size - (strlen($data) % $block_size);
return $data . str_repeat("\x00", $length);
}
public static function unpad_ZERO($data)
{
return rtrim($data, "\x00");
}
}
?>
As per the discussion above, in order to get the correct data back, I suspect you need to make the following change:
$decrypted = $AES->decrypt(base64_decode($message));
Edited to fix bug pointed out in the comments

Hashing from a public key to a bitcoin address in php

I am trying to follow the instructions required to turn a 65 byte public key into a bitcoin address using php. The instructions are quite explicit. Can anyone help me with the practicality of doing that in php?
Instructions are
1 - Take the corresponding public key generated with it (65 bytes, 1 byte 0x04, 32 bytes corresponding to X coordinate, 32 bytes corresponding to Y coordinate)
0450863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23522CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6
2 - Perform SHA-256 hashing on the public key
600FFE422B4E00731A59557A5CCA46CC183944191006324A447BDB2D98D4B408
3 - Perform RIPEMD-160 hashing on the result of SHA-256
010966776006953D5567439E5E39F86A0D273BEE
4 - Add version byte in front of RIPEMD-160 hash (0x00 for Main Network)
00010966776006953D5567439E5E39F86A0D273BEE
5 - Perform SHA-256 hash on the extended RIPEMD-160 result
445C7A8007A93D8733188288BB320A8FE2DEBD2AE1B47F0F50BC10BAE845C094
6 - Perform SHA-256 hash on the result of the previous SHA-256 hash
D61967F63C7DD183914A4AE452C9F6AD5D462CE3D277798075B107615C1A8A30
7 - Take the first 4 bytes of the second SHA-256 hash. This is the address checksum
D61967F6
8 - Add the 4 checksum bytes from point 7 at the end of extended RIPEMD-160 hash from point 4. This is the 25-byte binary Bitcoin Address.
00010966776006953D5567439E5E39F86A0D273BEED61967F6
9 - Convert the result from a byte string into a base58 string using Base58Check encoding. This is the most commonly used Bitcoin Address format
16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM
My first attempt is
// step 1
$publickey='0450863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23522CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6';
$step1=$publickey;
echo "step1 ".$publickey."<br>";
// step 2
$step2=hash("sha256",$step1);
echo "step2 ".$step2."<br>";
// step 3
$step3=hash('ripemd160',$step2);
echo "step3 ".$step3."<br>";
// step 4
$step4="00".$step3;
echo "step4 ".$step4."<br>";
// step 5
$step5=hash("sha256",$step4);
echo "step5 ".$step5."<br>";
// step 6
$step6=hash("sha256",$step5);
echo "step6 ".$step6."<br>";
// step 7
$checksum=substr($step6,0,8);
echo "step7 ".$checksum."<br>";
// step 8
$step8=$step4.$checksum;
echo "step8 ".$step8."<br>";
//step 9
$step9=base58_encode($step8);
echo "step9 ".$step9."<br><br>";
This fails at the first step. Any help appreciated.
This is the output
step1 0450863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23522CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6
step2 32511e82d56dcea68eb774094e25bab0f8bdd9bc1eca1ceeda38c7a43aceddce
step3 7528c664cdc34c5ce809778eb688d32f89a538c0
step4 007528c664cdc34c5ce809778eb688d32f89a538c0
step5 86e76f4ff0bf0387339ac70a552e0fed615f7def34cc4809df1429e243f6c1fa
step6 b885b7225b370e7ff27ee0afb4f89b52b8675d5dc342d63de3abe7535f86cadb
step7 b885b722
step8 007528c664cdc34c5ce809778eb688d32f89a538c0b885b722
step9 1
Base58 function is
function base58_encode($input)
{
$alphabet = '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ';
$base_count = strval(strlen($alphabet));
$encoded = '';
while (floatval($input) >= floatval($base_count))
{
$div = bcdiv($input, $base_count);
$mod = bcmod($input, $base_count);
$encoded = substr($alphabet, intval($mod), 1) . $encoded;
$input = $div;
}
if (floatval($input) > 0)
{
$encoded = substr($alphabet, intval($input), 1) . $encoded;
}
return($encoded);
}
Solution below with thanks to Sammitch for spotting syntax and providing the base conversions.
<?php
// step 1
$publickey='0450863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23522CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6';
$step1=hexStringToByteString($publickey);
echo "step1 ".$publickey."<br>";
// step 2
$step2=hash("sha256",$step1);
echo "step2 ".$step2."<br>";
// step 3
$step3=hash('ripemd160',hexStringToByteString($step2));
echo "step3 ".$step3."<br>";
// step 4
$step4="00".$step3;
echo "step4 ".$step4."<br>";
// step 5
$step5=hash("sha256",hexStringToByteString($step4));
echo "step5 ".$step5."<br>";
// step 6
$step6=hash("sha256",hexStringToByteString($step5));
echo "step6 ".$step6."<br>";
// step 7
$checksum=substr($step6,0,8);
echo "step7 ".$checksum."<br>";
// step 8
$step8=$step4.$checksum;
echo "step8 ".$step8."<br>";
// step 9
// base conversion is from hex to base58 via decimal.
// Leading hex zero converts to 1 in base58 but it is dropped
// in the intermediate decimal stage. Simply added back manually.
$step9="1".bc_base58_encode(bc_hexdec($step8));
echo "step9 ".$step9."<br><br>";
?>
hash requires a byte string not a hex string. hexStringToByteString is
function hexStringToByteString($hexString){
$len=strlen($hexString);
$byteString="";
for ($i=0;$i<$len;$i=$i+2){
$charnum=hexdec(substr($hexString,$i,2));
$byteString.=chr($charnum);
}
return $byteString;
}
base conversion (thanks to Sammitch - amended to use Bitcoin base58)
// BCmath version for huge numbers
function bc_arb_encode($num, $basestr) {
if( ! function_exists('bcadd') ) {
Throw new Exception('You need the BCmath extension.');
}
$base = strlen($basestr);
$rep = '';
while( true ){
if( strlen($num) < 2 ) {
if( intval($num) <= 0 ) {
break;
}
}
$rem = bcmod($num, $base);
$rep = $basestr[intval($rem)] . $rep;
$num = bcdiv(bcsub($num, $rem), $base);
}
return $rep;
}
function bc_arb_decode($num, $basestr) {
if( ! function_exists('bcadd') ) {
Throw new Exception('You need the BCmath extension.');
}
$base = strlen($basestr);
$dec = '0';
$num_arr = str_split((string)$num);
$cnt = strlen($num);
for($i=0; $i < $cnt; $i++) {
$pos = strpos($basestr, $num_arr[$i]);
if( $pos === false ) {
Throw new Exception(sprintf('Unknown character %s at offset %d', $num_arr[$i], $i));
}
$dec = bcadd(bcmul($dec, $base), $pos);
}
return $dec;
}
// base 58 alias
function bc_base58_encode($num) {
return bc_arb_encode($num, '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
}
function bc_base58_decode($num) {
return bc_arb_decode($num, '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
}
//hexdec with BCmath
function bc_hexdec($num) {
return bc_arb_decode(strtolower($num), '0123456789abcdef');
}
function bc_dechex($num) {
return bc_arb_encode($num, '0123456789abcdef');
}
final output
step1 0450863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23522CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6
step2 600ffe422b4e00731a59557a5cca46cc183944191006324a447bdb2d98d4b408
step3 010966776006953d5567439e5e39f86a0d273bee
step4 00010966776006953d5567439e5e39f86a0d273bee
step5 445c7a8007a93d8733188288bb320a8fe2debd2ae1b47f0f50bc10bae845c094
step6 d61967f63c7dd183914a4ae452c9f6ad5d462ce3d277798075b107615c1a8a30
step7 d61967f6
step8 00010966776006953d5567439e5e39f86a0d273beed61967f6
step9 16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM
Look carefully at your variable names. $publickey is not the same as $publicKey - note capitalisation.
Your problems are as follow:
Variable names $publickey and $publicKey are not equivalent.
$checksum=substr($step6,0,4); should be $checksum=substr($step6,0,8); because you need two hex chars to represent one byte.
$step8=$step4+$checksum; should be $step8=$step4.$checksum;
Also, I don't know where your base58_encode() function comes from, but I hope it uses BCmath, because the number represented by 00ba084d3f143f2896809d3f1d7dffed472b39d8de7a39cf51 [step 8's result] is too large for PHP to handle internally.
edit
I'm super bored at work today, here's my conversion code with bonus BCmath for ginormous [say, 58-digit?] numbers.
<?php
// original arbitrary encode function
function arb_encode($num, $basestr) {
$base = strlen($basestr);
$rep = '';
while($num > 0) {
$rem = $num % $base;
$rep = $basestr[$rem] . $rep;
$num = ($num - $rem) / $base;
}
return $rep;
}
function arb_decode($num, $basestr) {
$base = strlen($basestr);
$dec = 0;
$num_arr = str_split((string)$num);
$cnt = strlen($num);
for($i=0; $i < $cnt; $i++) {
$pos = strpos($basestr, $num_arr[$i]);
if( $pos === false ) {
Throw new Exception(sprintf('Unknown character %s at offset %d', $num_arr[$i], $i));
}
$dec = ($dec * $base) + $pos;
}
return $dec;
}
// BCmath version for huge numbers
function bc_arb_encode($num, $basestr) {
if( ! function_exists('bcadd') ) {
Throw new Exception('You need the BCmath extension.');
}
$base = strlen($basestr);
$rep = '';
while( true ){
if( strlen($num) < 2 ) {
if( intval($num) <= 0 ) { break; }
}
$rem = bcmod($num, $base);
$rep = $basestr[intval($rem)] . $rep;
$num = bcdiv(bcsub($num, $rem), $base);
}
return $rep;
}
function bc_arb_decode($num, $basestr) {
if( ! function_exists('bcadd') ) {
Throw new Exception('You need the BCmath extension.');
}
$base = strlen($basestr);
$dec = '0';
$num_arr = str_split((string)$num);
$cnt = strlen($num);
for($i=0; $i < $cnt; $i++) {
$pos = strpos($basestr, $num_arr[$i]);
if( $pos === false ) {
Throw new Exception(sprintf('Unknown character %s at offset %d', $num_arr[$i], $i));
}
$dec = bcadd(bcmul($dec, $base), $pos);
}
return $dec;
}
// base 58 alias
function bc_base58_encode($num) {
return bc_arb_encode($num, '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ');
}
function bc_base58_decode($num) {
return bc_arb_decode($num, '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ');
}
//hexdec with BCmath
function bc_hexdec($num) {
return bc_arb_decode(strtolower($num), '0123456789abcdef');
}
function bc_dechex($num) {
return bc_arb_encode($num, '0123456789abcdef');
}
// example
$orig = '00ba084d3f143f2896809d3f1d7dffed472b39d8de7a39cf51';
$bten = bc_hexdec($orig);
$base58 = bc_base58_encode($bten);
$backten = bc_base58_decode($base58);
$back = bc_dechex($backten);
echo "Orig: " . $orig . "\n";
echo "bten: " . $bten . "\n";
echo "58: " . $base58 . "\n";
echo "ag10: " . $backten . "\n";
echo "Back: " . $back . "\n";
edit2
Don't use base_convert() for numbers this large, it appears to be unreliable. I was writing the necessary bc_arb_decode() counterparts and found that the input and output were differing using base_convert() versus arb_convert($num, '0123456789abcdef'); and after comparing the results with Wolfram Alpha it seems that PHP is incorrectly converting the number.
Hex: 00ba084d3f143f2896809d3f1d7dffed472b39d8de7a39cf51
PHP's decode: 4561501878697786606686086062428080084446806606846864824262
Mine: 4561501878697784703577561586669353227270827349968709865297
Wolfram Alpha: 4561501878697784703577561586669353227270827349968709865297
You can see that PHP is way off. [1.9E42 aka 1.9 quintillion septillion] I've updated my code to include the arb_decode() functions which appear to do things correctly.
Very Important!
Replace this: '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ'
With this: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
Using the wrong code here would cause Bitcoin transactions to fail or worse, cause coins to disappear to a phantom wallet where they can never be retrieved.
I am not a developer but I confirmed the correction. The Base58 symbol chart is here, https://en.bitcoin.it/wiki/Base58Check_encoding
I checked my work here http://brainwallet.org/
Enter the passphrase: "test address" without the quotes.
The public key is then: 047969a753f71135d4c792f384e546cd508514024b4ee40d12a014019b77d1b292763dfb8a108cf7a7119f80ca4a06e81b92464f5d8a7544d52cd2e641023a96d7
Your address result : 1gBG1mbVtyNTgGZhggJ21A6mnjbNtqPCSr
My result: 1Gch1MBvUZotGhzHGGj21b6MNKBoURpdsS
brainwallet.org result: 1Gch1MBvUZotGhzHGGj21b6MNKBoURpdsS
I hope this saves someone from a time consuming or costly error.

How to convert OpenSSH public key file format to PEM

I have RSA public key in OpenSSH format:
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9xmJumsHeLEDcJwf3LYONZholP3+pDHJYen4w+gm8o1r7t6oq825Gmjr7pjsQ+ZDxWivkI4vMW9RyFevPg09ljW+V7lZInBpRtB6v1s8PdmV9YVk4R3S0e7sPMPXuM7ocPLh5yKZ9f7JZwQlpp4ww/RE7blbXywjwCxngT7+G+J6HJB0UcR8xR8t6z8qDrDTAJA7pFFFNliw9M+I8tbrFl8HmoyudOFsGsYOd5hjemy4ivW88XcXzfHJdKnmD9FHVZv/GUXgErVMHS25xLcJfPalm5R8BFQrgl8SiqXj9i2vEVct9ZGydG0/Zyh2eX98D82pJhgIBmpJC4JUGv+Mt user#host
How to convert in PHP this key into format suitable for openssl_pkey_get_public()?
It is easy to extract both RSA public key numbers (n and e), because second part of OpenSSH string is just base64 encoded key format described in RFC4253. So in fact the question is how to encode these numbers into PEM RSA public key format?
Solution:
function len($s)
{
$len = strlen($s);
if ($len < 0x80) {
return chr($len);
}
$data = dechex($len);
$data = pack('H*', (strlen($data) & 1 ? '0' : '') . $data);
return chr(strlen($data) | 0x80) . $data;
}
function openssh2pem($file)
{
list(,$data) = explode(' ', trim(file_get_contents($file)), 3);
$data = base64_decode($data);
list(,$alg_len) = unpack('N', substr($data, 0, 4));
$alg = substr($data, 4, $alg_len);
if ($alg !== 'ssh-rsa') {
return FALSE;
}
list(,$e_len) = unpack('N', substr($data, 4 + strlen($alg), 4));
$e = substr($data, 4 + strlen($alg) + 4, $e_len);
list(,$n_len) = unpack('N', substr($data, 4 + strlen($alg) + 4 + strlen($e), 4));
$n = substr($data, 4 + strlen($alg) + 4 + strlen($e) + 4, $n_len);
$algid = pack('H*', '06092a864886f70d0101010500'); // algorithm identifier (id, null)
$algid = pack('Ca*a*', 0x30, len($algid), $algid); // wrap it into sequence
$data = pack('Ca*a*Ca*a*', 0x02, len($n), $n, 0x02, len($e), $e); // numbers
$data = pack('Ca*a*', 0x30, len($data), $data); // wrap it into sequence
$data = "\x00" . $data; // don't know why, but needed
$data = pack('Ca*a*', 0x03, len($data), $data); // wrap it into bitstring
$data = $algid . $data; // prepend algid
$data = pack('Ca*a*', 0x30, len($data), $data); // wrap it into sequence
return "-----BEGIN PUBLIC KEY-----\n" .
chunk_split(base64_encode($data), 64, "\n") .
"-----END PUBLIC KEY-----\n";
}
Resources: https://www.rfc-editor.org/rfc/rfc3447#appendix-A.1, http://luca.ntop.org/Teaching/Appunti/asn1.html
This is an old question...but I just went through this exercise myself, and I've documented my solution here:
http://blog.oddbit.com/2011/05/08/converting-openssh-public-keys/
There's sample code there for producing a PKCS#1 RSA public key from an OpenSSH public key, which is slightly different from Jakub's solution (which produces an x.509 public key). I've tried to include explanations and links to additional documentation in the write-up.
I'm adding this here primarily because this question kept coming up in my searches for additional information, so I'm hoping someone out there will find useful my attempt to summarize things.

Categories