I have acquired a public key from a service provider and i now want to encrypt a password.
I have tried to encrypt this way
$publicKey = file_get_contents('cert.cer');
//$plaintext = "Safaricom132!";
$plaintext = "safaricom323!";
openssl_public_encrypt($plaintext, $encrypted, $publicKey, OPENSSL_PKCS1_PADDING);
echo base64_encode($encrypted);
I get this result
k97YxoV0uS08ok0n99id8PPp9kfSdebhdHaVroTmGF16hR70+U30xSrsRgIaVvHrzu535oH7Mz2TxkgQktq4TmVnxIhWbwBYDlVfnNoAUaHAW/oOYbF6bRR+vCHvMAI8TrQLaMK8XhVMjHqr27pmoqEyzEYC0sop+rp2T81CcWFlyJDr57GHuSoeka8pHMFSsGGvU7S+K0ZZ7xNQGOUpvT8skDMo3u7RUjbDk466dEtR1SUWfYTwVcrBCAFWI9ElfzFzgrWSr+ECelbU7n4D9dJegt9HvPhI2Nnc2q0V7DvHdwO+lHt7tKlM9xbxCTewiFHWjn9+lCAzwqnorHwN3g==
Should i specify the cipher i want to use or is the key i have generated a validly encrypted text?>
You have specified a cipher. Your certificate undoubtedly contains an RSA public key - so only RSA encryption is possible using this certificate. And you've specified PKCS#1 padding, which determines the encryption scheme used for RSA encryption.
No doubt you've generated a validly encrypted text. However, the only way to be 100% sure is to decrypt said ciphertext with the private key (after base 64 decoding the ciphertext, of course). If that returns your plaintext message - "safari323!" - then you know that decryption succeeded.
Note that PKCS#1 is not as secure as it was once thought to be. The current best of breed is OAEP padding or RSA-KEM.
Related
I am trying to encrypt a password to sent to through an API for authentication. My situation is quite similar to another encrypt example
The python part is form see the get_pwd_rsa function
Python part:
import rsa
key = rsa.PublicKey(n, e)
encropy_pwd = rsa.encrypt(message, key)
binascii.b2a_hex(encropy_pwd)
And I have try using phpseclib to solve my problem, but the encrypt result is not correct:
use phpseclib\Crypt\RSA;
$rsa = new RSA();
$rsa->loadKey([
'n' => new \phpseclib\Math\BigInteger($n, 16),
'e' => new \phpseclib\Math\BigInteger($e, 16),
]);
$ciphertext = $rsa->encrypt($message);
return bin2hex($ciphertext);
The encrypt result is not correct and I'm not sure which part am I missing.
To add to what James said, my suspicion is that whatever Python library you're using is doing PKCS1 padding if it's doing any padding at all.
Contrast this with phpseclib which uses OAEP padding by default. PKCS1 padding can be enabled but it defaults to the more secure OAEP padding.
Further, both PKCS1 and OAEP encryption are randomized. I say that because I kinda wonder if you're saying the PHP code is wrong because it doesn't give the same result as the python code. It shouldn't. If you run a round of RSA encryption with phpseclib twice with the same plaintext and key each time you'll get a different ciphertext each time. PKCS1 and OAEP were designed this way to protect against known plaintext attacks.
I noticed that we should store IV (Initialization Vector) when Encrypting in CBC mode (for example storing IV as a plain text in database next to the encrypted string)
But CodeIgniter's Encryption Class does not return any IV and it is not stored in database or anywhere else either, it simply takes a 32 character $key and $string and provides encrypted text:
$msg = 'My message to encrypt';
$key = 'super-secret-key';
$encrypted_string = $this->encrypt->encode($msg, $key);
My question is
What happens to the IV when using CodeIgniter's Encryption?
i'm asking because if it is dependent on the server or script or hidden somewhere, we cannot use the encrypted message anywhere else without the IV.
I've looked into the code and at least for version 2.1.0, the IV is simply prepended to the ciphertext. Since the IV is not supposed to be secret, but only random (to ensure semantic security), it can be sent in the clear.
CodeIgniter also implements the unusual _add_cipher_noise() function on the IV + ciphertext which changes both completely. It is a simple additional encryption method to hide the IV and prevent man-in-the-middle attacks on the first block of the ciphertext.
The usual solution for this is to authenticate the IV + ciphertext. CodeIgniter 3 seems to provide a function to add an authentication tag to the ciphertext derived through HKDF.
I'm working on replacing a legacy system that (among other things) receives SHA1 hashes of arbitrary files and signs them using a private key with a simple PHP web service.
It should look something like that:
$providedInput = '13A0227580C5DE137C2EBB2907A3F2D7F00CA71D';
// pseudo "= sha1(somefile.txt); file not available server side!
$expectedOutput = 'DBC9CC4CB0BECEE313BB100DD1AD39AEC045714D72767211FD574E3E3546EB55E77D2EBFE33BA2974BB74CE051608BFF45A73A52612C5FC418DD3A76CAC0AE0C8FB3FC6CE4F7A516013A9743A36424DDACFE889B3D45E86E6853FD9A55B5B4F0F0D8A574A0B244C0946A99B81CCBD1A7AF7C11072745B11C06AD680BE8AC4CB4';
// pseudo: "= openssl_sign(file_get_contents(somefile.txt), signature, privateKeID);
For the sake of simplicity I'm using PHP's built in openssl extention. The problem I'm running into is that openssl_sign seems to SHA1 hash the input data again internally according to this German manual entry on openssl_sign. The English entry is missing that info for some reason.
This produces the expected output ...
$privateKeyID = openssl_get_privatekey(file_get_contents($privateKey));
openssl_sign(file_get_contents("x.txt"), $signature, $privateKeyID);
var_dump(bin2hex($signature));
... but since I don't have access to the actual input files on server side it's not very helpful.
Is there a way around the additional hashing without 3rd party libs? I already tried to simply encrypt the hash received, but from How to compute RSA-SHA1(sha1WithRSAEncryption) value I understand encrypting and signing produce different output.
Update to make things more clear:
I'm recieving an SHA1 hash as input and the service has to convert it to a valid signature (using a private key) that can simply be verified using openssl_verify. The clients are out of reach, so changing their implementation is not possible.
From How to compute RSA-SHA1(sha1WithRSAEncryption) value:
If you reproduce this EM and use RSA_private_encrypt, then you will get the correct PKCS#1 v1.5 signature encoding, the same you would get with RSA_sign or even better, using the generic EVP_PKEY_sign.
I figured I could simply implement the DER encoding myself according to this specification, but the result (EM) seems too long to be encrypted with my key
// 1. Apply the hash function to the message M to produce a hash value H
$H = hex2bin($input); // web service receives sha1 hash of an arbitrary file as input
$emLen = 128; // 1024 rsa key
// 2. Encode the algorithm ID for the hash function and the hash value into
// an ASN.1 value of type DigestInfo
$algorithmIdentifier = pack('H*', '3021300906052b0e03021a05000414');
$digest = $H;
$digestInfo = $algorithmIdentifier.$digest;
$tLen = strlen($digestInfo);
// 3. error checks omitted ...
// 4. Generate an octet string PS consisting of emLen - tLen - 3 octets
// with hexadecimal value 0xff. The length of PS will be at least 8
// octets.
$ps = str_repeat(chr(0xFF), $emLen - $tLen - 3);
//5. Concatenate PS, the DER encoding T, and other padding to form the
// encoded message EM as
$em = "\0\1$ps\0$digestInfo";
if(!openssl_private_encrypt($em, $signature, $privateKeyID)) {
echo openssl_error_string();
}
else {
echo bin2hex($signature);
}
Output:
Error:0406C06E:rsa routines:RSA_padding_add_PKCS1_type_1:data too large for key size
Any hints?
UPDATE
As you can see in code below openssl_verify return 1 for result of openssl_sign and even for openssl_private_encrypt result. I tested it on my machine. This solution will work only if sha1 digest in digital signature is used.
// Content of file
$data = 'content of file somewhere far away';
// SHA1 hash from file - input data
$digest = hash('sha1', $data);
// private and public keys used for signing
$private_key = openssl_pkey_get_private('file://mykey.pem');
$public_key = openssl_pkey_get_public('file://mykey.pub');
// Encoded ASN1 structure for encryption
$der = pack('H*', '3021300906052b0e03021a05000414') . pack('H*', $digest);
// Signature without openssl_sign()
openssl_private_encrypt($der, $signature, $private_key);
// Signature with openssl_sign (from original data)
openssl_sign($data, $opensslSignature, $private_key);
// Verifying - both should return 1
var_dump(openssl_verify($data, $signature, $public_key));
var_dump(openssl_verify($data, $opensslSignature, $public_key));
I just captured DER encoded structure by decrypting openssl_sign() result.
ORIGINAL ANSWER
openssl_sign() creates digest from data because this is how digital signature works. Digital signature is always encrypted digest from data.
You can use openssl_private_encrypt() and openssl_public_decrypt() on your sha1 digest with no fear. In general, it is the same thing but yes, there is a difference. If you encrypt something on your own, the encryption process does not care about data and just encrypts them. It is on you to know that what you will decrypt later is sha1 digest for some data. In fact, it is just data encryption with private key, not true digital signature.
openssl_sign() creates digest from data and encrypts information about kind of digest and digest itself (this is ASN.1 DER structure from your link). This is because openssl_verify() needs to know what kind of digest was used when signing.
According to the English page of openssl_sign:
bool openssl_sign ( string $data , string &$signature , mixed $priv_key_id [, mixed $signature_alg = OPENSSL_ALGO_SHA1 ] )
I think the obvious suggestion is to use OPENSSL_ALGO_SHA256. See openssl_get_md_methods for a list of the supported algorithms.
I'm using Zend2 Crypt module to encrypt a data. Here's my code.
$cipher = BlockCipher::factory('mcrypt', array(
'algorithm' => 'aes',
));
$cipher->setKey('mypassphrase');
$encrypted = $cipher->encrypt('Hey, I am the secret data');
Cool, it works well! Now, I need to decrypt that $encrypted data (Hey, I am the secret data) in Python.
I am using pycrypto to do that. What the steps to decrypt data outside my PHP environment?
from Crypto.Cipher import AES
import base64
import hashlib
password = 'mypassphrase'
key = hashlib.sha256(password).digest()
decoded = base64.standard_b64decode(encrypted)
cipher = AES.new(key, AES.MODE_CBC)
data = cipher.decrypt(decoded)
I need to specify an IV because Zend uses MODE_CBC by default. How can I specify it in my Python code?
Here's the Zend2 documentation:
The output of the encryption is a string, encoded in Base64 (default), that contains the HMAC value, the IV vector, and the encrypted text. The encryption mode used is the CBC (with a random IV by default) and SHA256 as default hash algorithm of the HMAC. The Mcrypt adapter encrypts using the PKCS#7 padding mechanism by default. You can specify a different padding method using a special adapter for that (Zend\Crypt\Symmetric\Padding). The encryption and authentication keys used by the BlockCipher are generated with the PBKDF2 algorithm, used as key derivation function from the user’s key specified using the setKey() method.
Can someone help me to adapt my Python code to decrypt the data?
Thanks
I found a way to decrypt the data encrypted by Zend2. Here's my code:
from base64 import b64decode
from Crypto import Random
from Crypto.Cipher import AES
from Crypto.Hash import SHA256, HMAC
from Crypto.Protocol.KDF import PBKDF2
# The hmac starts from 0 to 64 (length).
hmac_size = 64
hmac = data[:hmac_size]
# The cipher text starts after the hmac to the end.
# The cipher text is base64 encoded, so I decoded it.
ciphertext = data[hmac_size:]
ciphertext = b64decode(ciphertext)
# The IV starts from 0 to 16 (length) of the ciphertext.
iv = ciphertext[:16]
# The key size is 256 bits -> 32 bytes.
key_size = 32
# The passphrase of the key.
password = 'mypassphrase'
# The key is generated using PBKDF2 Key Derivation Function.
# In the case of Zend2 Crypt module, the iteration number is 5000,
# the result length is the key_size * 2 (64) and the HMAC is computed
# using the SHA256 algorithm
the_hash = PBKDF2(password, iv, count=5000, dkLen=64, prf=lambda p, s:
HMAC.new(p, s, SHA256).digest())
# The key starts from 0 to key_size (32).
key = the_hash[:key_size]
# The hmac key starts after the key to the end.
key_hmac = the_hash[key_size:]
# HMAC verification
hmac_new = HMAC.new(key_hmac, 'aes%s' % ciphertext, SHA256).hexdigest()
if hmac_new != hmac:
raise Exception('HMAC verification failed.')
# Instanciate the cipher (AES CBC).
cipher = AES.new(key, AES.MODE_CBC, iv)
# It's time to decrypt the data! The ciphertext starts after the IV (so, 16 after).
data = cipher.decrypt(ciphertext[16:])
Mission succeeded!
why is the content of $encrypted every time different?
// aquire public key from server
$server_public_key = openssl_pkey_get_public(file_get_contents("C:\publickey.pem"));
// rsa encrypt
openssl_public_encrypt("123", $encrypted, $server_public_key);
also I have tried this one
$publicKey = "file://C:/publickey.pem";
$privateKey = "file://C:/privatekey.pem";
$plaintext = "String to encrypt";
openssl_public_encrypt($plaintext, $encrypted, $publicKey);
$transfer = base64_encode($encrypted);
openssl_private_decrypt($encrypted, $decrypted, $privateKey);
echo $transfer; //encrypted string
and $transfer is everytime a different string:...
Z1xyMUquARxcGjqjjSHNAm41CnHI02GXxLyFivvta8YhDkhRJdD4i3kx+8GElljdiSY/NMF9UD3ritWMLGmscdq/QyIf+geYxJFePNd1dNWg+V6zbAKRLaEpsU+aB87jiM/GjytLEkI63dku02BS0ZBgz9UZw/FDNaynV5bTTDM=
mRgLPsPtMoV9la7zzuU+cLzS5xMDp7QUmH6Iv4Sv4/FNjt62zcv9ZMWkfG3uVhS8Z1UDtGl+met1CYjBTcfjHCR6hahbwOkTCICXtkRQcc371vURW04XhQzMNgIIbvN5BBdmIyYI6alrS2vKUq7b3T0h8sJf36zh5CynYzyDCFU=
G5FhMoJGiUwEBvEOeZpDDrEXdxbWX5iaJ6F+VdYJ3CURPRMftskZNlDhat8gA5V0G+3nXVQZptkHjxMkOqPlmwJHjgIqAiFppHLpEKohyT9qNwkAR00Y6PiWrNUJPiEIZqXHAb8TS0AA0Quhc0UAwcc+I8NGOD59k8BrZE6Z5Ew=
The PKCS#1 encryption algorithm uses some random seed to make the cipher-text different every time.
This protects the cipher-text against several attacks, like frequency analysis, ciphertext matching. For example, if you were using a public key to encrypt all your password without randomness. All the same password will yield the same cipher-text. Someone can figure out all the popular passwords by checking the frequency of the cipher-text.
For symmetric key encryption, IV (Initial Vector) serves a similar purpose.