I am using phpseclib for RSA encryption http://phpseclib.sourceforge.net/.
This is my php code:
include('Math/BigInteger.php');
include('Crypt/RSA.php');
$message="123456";
$private_modulus = "272435F22706FA96DE26E980D22DFF67";
$private_exponent = "158753FF2AF4D1E5BBAB574D5AE6B54D";
$rsa = new Crypt_RSA();
$message = new Math_BigInteger(base64_decode($message), 256);
$private_modulus = new Math_BigInteger(base64_decode($private_modulus), 256);
$private_exponent = new Math_BigInteger(base64_decode($private_exponent), 256);
$rsa->loadKey(array('n' => $private_modulus, 'e' => $private_exponent));
$encryptedText=$rsa->encrypt($message);
echo $encryptedText;
However, encryptedText is blank. Any help?
You have some issues with public/private keys. By the look of the function $rsa->loadKey() comment, first parameter is string, but you have assigned some strange array...
/**
* Loads a public or private key
*
* Returns true on success and false on failure (ie. an incorrect password was provided or the key was malformed)
*
* #access public
* #param String $key
* #param Integer $type optional
*/
function loadKey($key, $type = false)
Example of working encrypt/decrypt with random public/private keys :
include('_seclib/Math/BigInteger.php');
include('_seclib/Crypt/RSA.php');
$rsa = new Crypt_RSA();
extract($rsa->createKey());
# encrypt
$message = '123456';
$rsa->loadKey($publickey);
$ciphertext = $rsa->encrypt($message);
var_dump($ciphertext);
# decrypt
$rsa->loadKey($privatekey);
var_dump($rsa->decrypt($ciphertext));
See documentation for more info.
When an encryption mode isn't explicitely defined it defaults to OAEP. OAEP requires the key be at least 2 * the hash size + 2. The default hash phpseclib uses is sha1, which has a length of 20 bytes. So the key needs to be at least 22 bytes long or 176 bits.
Yours is 16 bytes long or 128 bits.
So you either need to use a larger key or change the encryption mode to use PKCS1 padding. PKCS1 padding requires the modulo be at least 11 bytes.
It'd probably be useful if phpseclib displayed an error when the length of the chosen modulo was insufficient. I'll try to suggeste that to him.
Also, FWIW, textbook RSA doesn't impose any minimum length requirements, but phpseclib doesn't currently implement textbook RSA. I guess you can kinda get it by calling _exponentiate directly but that's the only way.
Textbook RSA is bad because it's vulnerable to certain types of attacks that randomized padding like PKCS1 / OAEP protect against.
Related
I'm trying to create an encrypted string according to one sample code which is given in PHP, my main problem is Node.js crypto module doesn't accept keys with more than 32 bytes length but PHP openssl_encrypt does, it seems that's why I'm getting Invalid key size error.
here is my js code:
let iv = sha1(await HelpersService.makeRandomNumber(null, null, 16));
iv = iv.substr(0, 16);
const text = bundledData;
const password = sha1(this.credentials.appSecret);
let salt = sha1(await HelpersService.makeRandomNumber(null, null, 4));
salt = salt.substr(0, 4);
const key = crypto.createHash('sha256').update(password + salt).digest('hex');
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
let encoded = cipher.update(text, 'utf8', 'hex');
encoded += cipher.final('hex');
and this is the PHP sample:
function generateCashOutAPIHashKey($app_secret ){
//remove plus(+) sign from gsm number.
$data = 'text';
$iv = substr(sha1(mt_rand()), 0, 16);
$password = sha1($app_secret);
$salt = substr(sha1(mt_rand()), 0, 4);
$saltWithPassword = hash('sha256', $password . $salt);
$encrypted = openssl_encrypt("$data", 'aes-256-cbc', "$saltWithPassword", null, $iv );
return $encrypted;
}
The key generated in the PHP and NodeJS code has a length of 64 bytes. The used encryption AES-256 needs a 32 bytes key. In the PHP code, openssl_encrypt() implicitly shortens the 64 bytes key to 32 bytes. In the NodeJS code this has to be done explicitly:
const key = crypto.createHash('sha256').update(password + salt).digest('hex').substr(0, 32);
Also, openssl_encrypt() returns the ciphertext Base64 encoded by default. In the NodeJS code the result is returned hex encoded. Here you have to change the output encoding from 'hex' to 'base64' in the update() and final() call:
let encoded = cipher.update(text, 'utf8', 'base64');
encoded += cipher.final('base64');
Please note that the PHP reference code has a number of vulnerabilities:
mt_rand() is not a cryptographically secure pseudorandom number generator (CSPRNG). PHP provides cryptographically secure methods for deriving a random IV / salt, e.g. random_bytes() or random_int().
The key is inferred using SHA-256. It is more secure to use a reliable key derivation function such as PBKDF2. A 4 bytes salt is generally too small.
SHA1 is considered insecure in most contexts and must no longer be used.
Anyone have a problem with the following ?
crypto.createHash('sha256').update(password +
salt).digest('hex').substr(0, 32);
I understand the intent of needing a 32 byte value produced by sha256 but if you hex encode and then take the first 32 characters of the string you have SIGNIFICANTLY reduced the value of this. You now have a string 32 characters long that has only 16 possible values (0-9a-f) for each character. You really want a 32 byte value that has 256 possible values for each byte. You have changed your key security of your encryption key from the intended 256^32 (1.1E77) to 16^32 (3.4E38).
crypto.createHash('sha256').update('').digest('hex');
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
crypto.createHash('sha256').update('').digest('hex').substr(0,32);
'e3b0c44298fc1c149afbf4c8996fb924'
Find a way to use the byte values produced by SHA256.
I have this php code
$plain_text = "abc";
$salt = "123";
echo $encrypted_text = openssl_encrypt($plain_text, "AES-128-ECB", $salt);
// result: kR/1uaFarptS5+n951MVsQ==
I have tried several methods (classes and functions) on vb.net, but the result of the encryption with this language is everytime not the same as above using php.
For example this one:
Public Function AES_Encrypt (ByVal input As String, ByVal pass As String) As String
Dim AES As New System.Security.Cryptography.RijndaelManaged
Dim Hash_AES As New System.Security.Cryptography.MD5CryptoServiceProvider
Dim encrypted As String = ""
Try
Dim hash (31) As Byte
Dim temp As Byte () = Hash_AES.ComputeHash (System.Text.ASCIIEncoding.ASCII.GetBytes (pass))
Array.Copy (temp, 0, hash, 0, 16)
Array.Copy (temp, 0, hash, 15, 16)
AES.Key = hash
AES.Mode = Security.Cryptography.CipherMode.ECB
Dim DESEncrypter As System.Security.Cryptography.ICryptoTransform = AES.CreateEncryptor
Dim Buffer As Byte () = System.Text.ASCIIEncoding.ASCII.GetBytes (input)
encrypted = Convert.ToBase64String (DESEncrypter.TransformFinalBlock (Buffer, 0, Buffer.Length))
Return encrypted
Catch ex As Exception
End Try
End Function
sEnc = AES_Encrypt("abc", "123")
Console.WriteLine(sEnc)
'result: Z3hCHcS0b2zJ7fEod3jcrw==
Please, with vb.net (no C#), how can I get the result "kR/1uaFarptS5+n951MVsQ==" which encryption of the text "abc" and salt "123" using the algorithm "AES-128-ECB"?
Due to the specification AES-128-ECB in the PHP code, AES-128 is used in ECB mode, i.e. the key is 16 bytes long. But since only a 3 bytes large key is applied (123), PHP pads to the necessary size of 16 bytes with 0x00 values. Note that if the key is too long, it will be truncated.
In the VB code a 32 bytes key is used. Since in .NET the keysize determines the AES variant, AES-256 is applied. Moreover, the passed key is not used directly, but the actual key is derived from the passed value with the digest MD5.
So that the VB code returns the result of the PHP code, the logic of the PHP code must be implemented in the VB code:
...
'Dim hash(31) As Byte
'Dim temp As Byte() = Hash_AES.ComputeHash(System.Text.ASCIIEncoding.ASCII.GetBytes(pass))
'Array.Copy(temp, 0, hash, 0, 16)
'Array.Copy(temp, 0, hash, 15, 16)
'AES.Key = hash
Dim keyPadded(15) As Byte
Dim key = System.Text.ASCIIEncoding.ASCII.GetBytes(pass)
Array.Copy(key, 0, keyPadded, 0, Math.Min(16, key.Length))
AES.Key = keyPadded
...
A few remarks:
In the PHP code the key is called $salt. This is misleading, because a salt has a different meaning.
The ECB mode is generally insecure.
AES-128 uses a 16 bytes key. 123 is not a strong key (but maybe this is just a dummy value).
If 123 does not represent a key, but a password from which a key is derived, then in general you should not use MD5, but specially designed algorithms like PBKDF2 or Argon2, see also here.
I have a private key encrypted script with a password and here is its output:
{
"iv":"Ra6kDXvh2DBiZ0r37pNuzg==",
"v":1,
"iter":10000,
"ks":256,
"ts":64,
"mode":"ccm",
"adata":"",
"cipher":"aes",
"salt":"pNN1xP7SZks=",
"ct":"Sd8p3C3vPuW+LD
nO9GwltDnqGOHg7+qguaEjQxzidEh5RNDh7bodJfmzmoB4DjFYQ4Qi8ferWoVV6bwJ2Q9/BnqI+
X4A1MQY/HgVbtc9AnXj1EczsKxsUxG/ET7W+OBGQGLddzKVC38ACRg9q0NjOieOH0yTx64="
}
I dont know exactly whats name of this type of encryption and I want to know how to decrypt it using a password and PHP.
According to my research, it can be decrypted by the openssl_decrypt function.
But I couldn't find how to use my parameters in this function.
For example, I have a key called salt in the json that I have and I don't know what to do with it.
Also, in the openssl_decrypt function, there is an input argument called tag. I don't know the json key that it belongs to.
This is a sample of the code I'm using:
$ct = 'Sd8p3C3vPuW+LD
nO9GwltDnqGOHg7+qguaEjQxzidEh5RNDh7bodJfmzmoB4DjFYQ4Qi8ferWoVV6bwJ2Q9/BnqI+
X4A1MQY/HgVbtc9AnXj1EczsKxsUxG/ET7W+OBGQGLddzKVC38ACRg9q0NjOieOH0yTx64=';
$method = 'aes-256-ccm';
$password = 'Qw370207610';
$options = 0;
$iv = base64_decode('Ra6kDXvh2DBiZ0r37pNuzg==');
$output = openssl_decrypt($ct, $method, $password, $options, $iv);
And I received this error:
openssl_decrypt(): Setting of IV length for AEAD mode failed
UPDATE:
So I have gethered that for producing the third parameter (key) that is used in openssl_decrypt, I should act like this:
$ks = 256;
$key_length = $ks/8;
$password = 'Qw370207610';
$salt_base64 = 'pNN1xP7SZks=';
$salt = base64_decode($salt_base64);
$iterations = 10000;
$digest_algorithm = 'sha256';
$key = openssl_pbkdf2 ( $password , $salt , $key_length , $iterations , $digest_algorithm );
And then it can be decrypted in this way:
$ct_base64 = 'Sd8p3C3vPuW+LD
nO9GwltDnqGOHg7+qguaEjQxzidEh5RNDh7bodJfmzmoB4DjFYQ4Qi8ferWoVV6bwJ2Q9/BnqI+
X4A1MQY/HgVbtc9AnXj1EczsKxsUxG/ET7W+OBGQGLddzKVC38ACRg9q0NjOieOH0yTx64=';
$ct = base64_decode($ct_base64);
$ts = 64;
$tag_length = $ts/8;
$tag = substr($ct,-$tag_length);
$ccm = substr($ct,0,-$tag_length);
$method = 'aes-256-ccm';
$options = OPENSSL_RAW_DATA;
$iv_base64 = 'Ra6kDXvh2DBiZ0r37pNuzg==';
$iv = base64_decode($iv_base64); // 16 bytes length
$output = openssl_decrypt($ccm, $method, $key, $options, $iv, $tag);
However in PHP, for decrypting aes-ccm, there is just openssl, and they haven't offered another library.
On the other hand, these functions don't accept an IV (initialization vector) larger than 12 bytes. Because IV of my encrypted message is 16 bytes and it can not be decrypted in PHP at all !!
Have PHP developers not thought about this?
I have never had such problems in nodejs, but I always face some kind of restriction in PHP.
No, you have a password encrypted script, where a secret key is generated from that password. And it was generated using SJCL or a compatible library, demonstration here.
The salt and iteration count iter are input to the PBKDF2 function, which generates the AES key.
I hope you can progress using this information, because SO is not a code delivery service. Fortunately I know that OpenSSL contains PBKDF2, but I'm not sure if it is exposed to you.
please try this : From mcrypt_decrypt to openssl_decrypt
and for this you don't need the value of $iv if you give your data raw-value it will convert into base64 and other operation will be done by your method and algorithm. I think this might help you.
I am trying to encrypt a string using openssl_encrypt in PHP but it keeps returning FALSE.
$encrypted = openssl_encrypt('1234', 'AES-256-CBC', 'kGJeGF2hEQ', OPENSSL_ZERO_PADDING, '1234123412341234');
What am I doing wrong?
On top of answers posted, which are excellent, the code you're after, given your input parameters would be the following:
$plaintext = '1234';
$cipher = 'AES-256-CBC';
$key = 'this is a bad key';
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($cipher));
$encrypted = openssl_encrypt($plaintext, $cipher, $key, 0, $iv);
if(false === $encrypted)
{
echo openssl_error_string();
die;
}
$decrypted = openssl_decrypt($encrypted, $cipher, $key, 0, $iv);
$result = $decrypted === $plaintext;
print $result ? 'Everything is fine' : 'Well, we did not decrypt good, did we?';
Having written the above, I advise against using it and instead, please use a tested library designed to handle the complexities of encryption and decryption for you.
I suggest using defuse/php-encryption
php > var_dump (openssl_encrypt('1234', 'AES-256-CBC', 'kGJeGF2hEQ', OPENSSL_ZERO_PADDING, '1234123412341234'));
php shell code:1:
bool(false)
php > var_dump (openssl_error_string ());
php shell code:1:
string(94) "error:0607F08A:digital envelope routines:EVP_EncryptFinal_ex:data not multiple of block length"
It seems that the cypher you're using requires that the data you're encrypting has a length that's an exact multiple of the block length. With some experimentation I found that 1234123412341234 is successfully encrypted.
I don't know if this is a universal feature of all openssl encryption schemes, or whether it's something that's specific to certain schemes. In the former case you'll need to pad the input to a multiple of the block size. If the latter is true then you can either pad, or switch to a different encryption scheme that doesn't impose the same restrictions on the input.
For padding you need to find out what the blocksize of your chosen cypher is (I don't know if there's an openssl function or constant provided for that), then work out how many characters you need to pad your input string by.
Note that the following example assumes that a) there's some way of getting the blocksize programmatically (if not then you'll have to hard-code that yourself) and b) you're working with a byte-oriented character format (unicode might cause issues!)
$plaintext = "Hello, I'm some plaintext!";
$blocksize = function_that_gets_a_blocksize_for_a_given_cypher ($cypher);
$strlen = strlen ($plaintext);
$pad = $blocksize - ($strlen % $blocksize);
// If the string length is already a multiple of the blocksize then we don't need to do anything
if ($pad === $blocksize) {
$pad = 0;
}
$plaintext = str_pad ($plaintext, $strlen + $pad);
As for your code, this suggests you need to implement some error detection into it (but be careful what you actually log/echo out as part of the error detection!).
$encrypted = openssl_encrypt('1234', 'AES-256-CBC', 'kGJeGF2hEQ', OPENSSL_ZERO_PADDING, '1234123412341234');
if (false === $encrypted) {
error_log ("Encryption failed! " . openssl_error_string ());
}
Since block ciphers such as AES require input data to be an exact multiple of the block size (16-bytes for AES) padding is necessary. The usual method is just to specify PKCS#7 (née PKCS#5) by passing it as an option and the padding will be automatically added on encryption and removed on decryption. Zero padding (OPENSSL_ZERO_PADDING) is a poor solution since it will not work for binary data.
The IV needs to be block size, 8-bytes for AES. Do not rely on the implementation for padding.
The key should be the exact size specified, valid block sizes foe AES are 128, 192 or 256 bits (16, 24 or 32 bytes). Do not rely on the implementation for padding.
Before start fixing this bug, check all extension which is required for openssl_encrypt/decrypt is enabled?
class AnswerEncryption
{
const CURRENT_ALGO = 'AES-128-ECB';
const CIPHER='A?N#G+KbPe778mYq3t6w9z$C&F!J#jcQ';
CONST IV='1234567890123455';
/**
* #param null $Value
* #param null $cipher
* #return false|string
*/
public static function Encrypt($Value=null){
$iv = substr(self::IV, 0, 16);
return (openssl_encrypt($Value,self::CURRENT_ALGO,self::CIPHER,0,$iv));
}
/**
* #param null $Value
* #return int
*/
public static function Decrypt($Value=null): int
{
$iv = substr(self::IV, 0, 16);
return intval(openssl_decrypt($Value,self::CURRENT_ALGO,self::CIPHER,0,$iv));
}
}
in the decrypt method, I want the integer value, so you can change it accordingly
I want to change the crypt-functions in my php-app from mcrypt to openssl. Now I'm missing a function like mcrypt_enc_get_key_size() in openssl? How do I can read the max. keysize of a cypher-method in openssl?
Example: blowfish(CFB)
mcrypt_enc_get_key_size() returns 56 (Bytes) => 448bit
Any idea?
There is no such function with OpenSSL sadly. One option is to check the key size for each of the supported ciphers and use a switch. If you favor AES, you can do something like this.
$method = "AES-256-CBC"; // Or whatever you want
if (preg_match("/^aes-?([0-9]+)/i", $method, $matches)) {
// AES has the key size in it's name as bits
$keySize = $matches[1] / 8;
} else {
$ivSize = openssl_cipher_iv_length($method);
if ($ivSize > 0) {
/*
* This will fit will with most.
* A few might get a larger key than required, but larger is better than smaller
* since larger keys just get's downsized rather than padded.
*
*/
$keySize = $ivSize * 2;
} else {
// Defaults to 128 when IV is not used
$keySize = 16;
}
}
For example.
BF uses 64bit block size and will in this case get a 128bit keysize. It requires 32bit and takes up to 448bits.
CAST5 uses 64bit block size and requires between 40bit and 128bit key size, in this case it will get 128bit.
It's not perfect, but it will work. Or like mentioned above, you can always check the supported ciphers on http://php.net/manual/en/function.openssl-get-cipher-methods.php and manually search for and add max key size for each within a switch or similar.