3DES CB Encryption Dynamic Key - php

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

Related

Can't encrypt/decrypt on PHP equivalent NodeJS crypto

I'm having some trouble to get work an AES-256-CTR encrypt/decrypt in PHP, having a previosly encrypted string made with NodeJS crypto.
Here my JS code:
var crypto = require('crypto'),
algorithm = 'aes-256-ctr',
password = 'd6F3Efeq';
function encrypt(text){
var cipher = crypto.createCipher(algorithm,password)
var crypted = cipher.update(text,'utf8','hex')
crypted += cipher.final('hex');
return crypted;
}
function decrypt(text){
var decipher = crypto.createDecipher(algorithm,password)
var dec = decipher.update(text,'hex','utf8')
dec += decipher.final('utf8');
return dec;
}
Here my PHP code:
$text = "pereira";
echo bin2hex(openssl_encrypt($text, "aes-256-ctr", "d6F3Efeq",OPENSSL_RAW_DATA));
The JS's version return this on encrypt:
148bc695286379
The PHP's version return this on my encrypt test:
2f2ad5bb09fb56
Am I missing something here? Obviosly, I neither can decrypt correctly in PHP.
Thanks in advance.
You must set iv (initialization vector) in both side.
Node JS code:
var crypto = require('crypto'),
password = '1234567890abcdef1234567890abcdef',
iv = '1234567890abcdef',
text = "pereira";
function encrypt(iv, text, password){
var cipher = crypto.createCipheriv('aes-256-ctr', password, iv)
var crypted = cipher.update(text,'utf8','hex')
crypted += cipher.final('hex');
return crypted;
}
function decrypt(iv, text, password){
var decipher = crypto.createDecipheriv('aes-256-ctr', password, iv)
var dec = decipher.update(text,'hex','utf8')
dec += decipher.final('utf8');
return dec;
}
console.log(encrypt(iv, text, password));
And PHP code:
$text = 'pereira';
$algorithm = 'aes-256-ctr';
$password = '1234567890abcdef1234567890abcdef'; //node js required 32 byte length key
$iv = '1234567890abcdef'; //must be 16 byte length
echo bin2hex(openssl_encrypt($text, $algorithm, $password, OPENSSL_RAW_DATA, $iv));
Finally I got a solution for my question, thanks to #Alex K and #Artjom B.
As I mention in a comment, I must get a PHP encrypt/descrypt compatible with the NodeJS createCipher because the NodeJS application is a thrid party one.
So, the JS code is like this:
var crypto = require('crypto'),
algorithm = 'aes-256-ctr',
password = 'd6F3Efeq';
function encrypt(text){
var cipher = crypto.createCipher(algorithm,password)
var crypted = cipher.update(text,'utf8','hex')
crypted += cipher.final('hex');
return crypted;
}
function decrypt(text){
var decipher = crypto.createDecipher(algorithm,password)
var dec = decipher.update(text,'hex','utf8')
dec += decipher.final('utf8');
return dec;
}
I wrote a little PHP class to achieve this encrypt/decrypt job (Based on #Artjon's solution):
class Crypto
{
const METHOD = 'aes-256-ctr';
public function encrypt($plaintext, $password, $salt='', $encode = false)
{
$keyAndIV = self::evpKDF($password, $salt);
$ciphertext = openssl_encrypt(
$plaintext,
self::METHOD,
$keyAndIV["key"],
OPENSSL_RAW_DATA,
$keyAndIV["iv"]
);
$ciphertext = bin2hex($ciphertext);
if ($encode)
{
$ciphertext = base64_encode($ciphertext);
}
return $ciphertext;
}
public function decrypt($ciphertext, $password, $salt='', $encoded = false)
{
if ( $encoded )
{
$ciphertext = base64_decode($ciphertext, true);
if ($ciphertext === false)
{
throw new Exception('Encryption failure');
}
}
$ciphertext = hex2bin($ciphertext);
$keyAndIV = self::evpKDF($password, $salt);
$plaintext = openssl_decrypt(
$ciphertext,
self::METHOD,
$keyAndIV["key"],
OPENSSL_RAW_DATA,
$keyAndIV["iv"]
);
return $plaintext;
}
public function evpKDF($password, $salt, $keySize = 8, $ivSize = 4, $iterations = 1, $hashAlgorithm = "md5")
{
$targetKeySize = $keySize + $ivSize;
$derivedBytes = "";
$numberOfDerivedWords = 0;
$block = NULL;
$hasher = hash_init($hashAlgorithm);
while ($numberOfDerivedWords < $targetKeySize)
{
if ($block != NULL)
{
hash_update($hasher, $block);
}
hash_update($hasher, $password);
hash_update($hasher, $salt);
$block = hash_final($hasher, TRUE);
$hasher = hash_init($hashAlgorithm);
// Iterations
for ($i = 1; $i < $iterations; $i++)
{
hash_update($hasher, $block);
$block = hash_final($hasher, TRUE);
$hasher = hash_init($hashAlgorithm);
}
$derivedBytes .= substr($block, 0, min(strlen($block), ($targetKeySize - $numberOfDerivedWords) * 4));
$numberOfDerivedWords += strlen($block)/4;
}
return array(
"key" => substr($derivedBytes, 0, $keySize * 4),
"iv" => substr($derivedBytes, $keySize * 4, $ivSize * 4)
);
}
}
According to the NodeJS documentation, It derives the IV from the password with no salt, so I used an empty string as salt.
I'm using this PHP class in CodeIgniter like this:
$this->load->library("Crypto");
$plain_text = "pereira";
$password = "d6F3Efeq";
$enc_text = $this->crypto->encrypt($plain_text,$password);
$pla_text = $this->crypto->decrypt($enc_text,$password);
echo $enc_text."<br>";
echo $pla_text;
Both of them (NodeJS and PHP) return the same result for encrypt and decrypt function:
pereira = 148bc695286379
Regards.

PHP invalid hash tiger192 3

I am using a php code with php 5.4 and i am trying to hash a string with tiger192,3. I am not getting a correct hash after all..
$keyLength = 24;
$keyCharacters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$string = '';
$key = '';
$keyHash = '';
$first8Hash = '';
$uniqueKey = false;
while ($uniqueKey == false)
{
for ($p = 0; $p < $keyLength; $p++)
{
$string .= $keyCharacters[mt_rand(0, strlen($keyCharacters)-1)];
}
$key = $string;
$keyHash = hash('tiger192,3', $key);
$first8Hash = substr($keyHash, 0, 16);
$sql = "SELECT * FROM `penkeys` WHERE first8 = '" . $first8Hash . "'";
$result = $db->sql_query($sql);
while ($keyrow = $db->sql_fetchrow($result))
{
$uniqueKey = false;
}
$db->sql_freeresult($result);
$uniqueKey = true;
}
I am hashing the string nQ5GcLMsOlPIaUYJOMkmjo7f
I should be getting babcb7d489332aee9c554a7a654bb65b4dd892e5b80e0156 but i am getting ee2a3389d4b7bcba5bb64b657a4a559c56010eb8e592d84d.
Can you help me?
This is an interesting case where both answers (both the php answer and the timestampgenerator answer) are correct. In PHP >= 5.4, the tiger hashes use big-endian byte notation and in PHP < 5.4, it presumably used little-endian byte notation (noted in the changelog http://php.net/manual/en/function.hash.php). The function below is in the php docs and oldtiger gives babcb7d489332aee9c554a7a654bb65b4dd892e5b80e0156 as the result.
function old_tiger($data = "", $width=192, $rounds = 3) {
return substr(
implode(
array_map(
function ($h) {
return str_pad(bin2hex(strrev($h)), 16, "0");
},
str_split(hash("tiger192,$rounds", $data, true), 8)
)
),
0, 48-(192-$width)/4
);
}
echo hash('tiger192,3', 'a-string'), PHP_EOL;
echo old_tiger('a-string'), PHP_EOL;
If you notice, both babcb7d489332aee9c554a7a654bb65b4dd892e5b80e0156 and ee2a3389d4b7bcba5bb64b657a4a559c56010eb8e592d84d are anagrams of each other which is more than a coincidence. The endianness is changed between the two strings. Each set of 8 bytes is in byte-reversed order.
ee-2a-33-89-d4-b7-bc-ba becomes ba-bc-b7-d4-89-33-2a-ee. The byte-order endieness is switched for each 64-bit(8 byte) word.

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

Encrypt and Decrypt text with RSA in 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;
}

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