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.
Related
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.
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.
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;
}
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;
}
Let's say I have a byte-stream in which I know the location of a 64-bit value (a 64-bit nonce). The byte-order is Little-Endian. As PHP's integer data-type is limited to 32-bit (at least on 32-bit operating systems) how would I convert the byte-sequence into a PHP numeric representation (float would be sufficient I think)?
$serverChallenge = substr($bytes, 24, 8);
// $serverChallenge now contains the byte-sequence
// of which I know that it's a 64-bit value
Just looked up the code for Zend_Crypt_Math_BigInteger_Bcmath and Zend_Crypt_Math_BigInteger_Gmp which deals with this problem:
Using BCmath (Big-Endian)
This is essentially the solution posted by Chad Birch.
public static function bc_binaryToInteger($operand)
{
$result = '0';
while (strlen($operand)) {
$ord = ord(substr($operand, 0, 1));
$result = bcadd(bcmul($result, 256), $ord);
$operand = substr($operand, 1);
}
return $result;
}
Using GMP (Big-Endian)
Same algorithem - just different function names.
public static function gmp_binaryToInteger($operand)
{
$result = '0';
while (strlen($operand)) {
$ord = ord(substr($operand, 0, 1));
$result = gmp_add(gmp_mul($result, 256), $ord);
$operand = substr($operand, 1);
}
return gmp_strval($result);
}
Changing the algorithem to use Litte-Endian byte-order is quite simple: just read the binary data from end to start:
Using BCmath (Litte-Endian)
public static function bc_binaryToInteger($operand)
{
// Just reverse the binray data
$operand = strrev($operand);
$result = '0';
while (strlen($operand)) {
$ord = ord(substr($operand, 0, 1));
$result = bcadd(bcmul($result, 256), $ord);
$operand = substr($operand, 1);
}
return $result;
}
Using GMP (Litte-Endian)
public static function gmp_binaryToInteger($operand)
{
// Just reverse the binray data
$operand = strrev($operand);
$result = '0';
while (strlen($operand)) {
$ord = ord(substr($operand, 0, 1));
$result = gmp_add(gmp_mul($result, 256), $ord);
$operand = substr($operand, 1);
}
return gmp_strval($result);
}
This seems like a total hack, but it should do the job, assuming you have the BC Math functions that daemonmoi recommended:
$result = "0";
for ($i = strlen($serverChallenge) - 1; $i >= 0; $i--)
{
$result = bcmul($result, 256); // shift result
$nextByte = (string)(ord($serverChallenge[$i]));
$result = bcadd($result, $nextByte);
}
Two years late to the party, but if anyone still cares:
unpack is the built-in way to go here, you can unpack it as a couple of 32-bit ints, or as a double.
I know this is not quite the answer to the question, but check out the BC Math functions to handle big numbers.