I need to use pkcs8 private key in my project as example below, I can't found any library or method relate to pkcs8 signature only pkcs1 are available for the Crypto.Signature.
My python code example:
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA
from Crypto.PublicKey import RSA
privatekey='''-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAOuxJs5RD6s2ECEV
C38Jfj6xBBrcX6W1Qy+GLiXhh+wKj650BPSerQNQI+XPx/+uUemk/dJM3RMm75Di
mXJlp7/5LY11VyKSqS+x6ELIvWz8lo2OhHW6/wU4jiQ1MlburYbn/pRRClGGA8i4
FjHMvMu9/QCNPlNTlf7qJDO6lDVlAgMBAAECgYB0iP76/DGXIgAPm2w3v+Xf8X5q
GZRhRqKVmO6wZDbkisRIKa1ZlitNfA6DzpzA2tw9fgrSNJcKpTHGnYPpgEHUQrcw
t471yuAvzAOBffaqVjIt7vKyL3QnkNfxWenliasacX3olwBpKPrkvG75H2KBe9S0
WKpju1Cm6MebAJ3z8QJBAP8gSx3l9nNTqJcBBmP+TGDcTL9Sif2nBZrU/pWnzzop
HolaFIQTYXPIj8rMoVT/c0gLo1gcdHoSUJTbB0WO7V8CQQDsf9FAWCI3fkWgS/fW
21ApO6B07/388CpNHOTjvfsdlent4yXLL+Guh5apMH4Oq0tkGkEij5YPG1EZSYXi
48+7AkEAiX3m3ZMMMXTZe5/CyOrIUL8I4WbjFP8JJzs4hICuTmLQoScZvWAQeeyR
ibKkE4GjqCUVf6u+Hfd20/ICRjtTswJANCjEh8JoWYDZ7k6S7KoV9eIWs3OyurRl
P/idarUdyxqjKzorvbJjvdBdpBbz1lxlFkDMGMk+OTq3GjKi+rVvvQJAGhBieR2Q
69JNeoOg7J8Xk7evNBJHwtJfRDQXKJpN5YZsKclf6oRaYFEQOM6ThuNoYHISfGKw
UbDoUsPZoQFPtQ==
-----END PRIVATE KEY-----'''
signstr = testdata123
key = RSA.importKey(privatekey)
h = SHA.new(signstr)
signer = PKCS1_v1_5.new(key)
signature = signer.sign(h)
sign = base64.b64encode(signature)
The signature generated by code above do not match the public key in the api system I am using.
For PHP side I am able to sign correctly. The working code for PHP as below:
$private_key= openssl_get_privatekey($private_key);
openssl_sign($signStr,$sign_info,$private_key,OPENSSL_ALGO_MD5);
$sign = base64_encode($sign_info);
Any advice on how to sign with RSA pkcs8 format private key using python is much appreciated.
Hash algorithm
Of course, it must be different, because you're using different hash algorithms.
Your Python code uses SHA-1 and your PHP code uses MD5. The default hash algorithm in PHP is OPENSSL_ALGO_SHA1 as the documentation describes:
if (!openssl_sign($signStr, $sign_info, $private_key)) {
echo "Failed to sign";
}
Key encoding
There are many ways of encoding a private key. Two of the most common ways are PKCS#1 and PKCS#8 encodings. See more information on that here.
The key encoding is not a concern here, because both pycrypto and openssl use the correct one here.
Padding
There are two common padding schemes that are used for signing: PKCS#1 v1.5 padding and RSA-PSS (technically RSASSA-PKCS1-V1_5 and RSASSA-PSS). Both of them are defined in PKCS#1, but the latter was introduced in PKCS#1 v2.x.
Pycrypto supports both padding schemes, but openssl only supports PKCS#1 v1.5 in PHP.
Compatibility
If you want to check for compatibility of the same algorithm between different languages, you need to sign in one using the private key and verify the signature in the other using the public key. After you've done that, you can try the other way around if you also need the other direction.
This is only relevant for RSA-PSS, because that is randomized.
If you're using PKCS#1 v1.5 padding which is not randomized, you should be able to create a signature in both Python and PHP and compare them directly to check for compatibility.
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 have a RSA Public Key in this format:
OpenSSLRSAPublicKey{modulus=9ee9f82dd8429d9fa7f091c1d375b9c289bcf2c39ec57e175a2998b4bdd083465ef0fe6c7955c821b7e883929d017a9164a60290f1622f664a72096f5d2ffda7c7825c3d657c2d13d177445fa6cdd5d68b96346006a96040f5b09baae56d0c3efeaa77d57602f69018f5cefd60cb5c71b6b6f8a4b0472e8740367266917d8c13,publicExponent=10001}
So, I have the modulus and the exponent.
How can I convert this in a format that is accepted by openssl_encrypt() in PHP?
I have searched on Google and didn't find anything good. Isn't there an easy way to format it?
Assuming you mean openssl_public_encrypt() instead of openssl_encrypt(), then try this (using phpseclib 1.0):
<?php
include('Crypt/RSA.php')
include('Math/BigInteger.php');
$rsa = new Crypt_RSA();
$rsa->loadKey(array(
'n' => new Math_BigInteger('9ee9f82dd8429d9fa7f091c1d375b9c289bcf2c39ec57e175a2998b4bdd083465ef0fe6c7955c821b7e883929d017a9164a60290f1622f664a72096f5d2ffda7c7825c3d657c2d13d177445fa6cdd5d68b96346006a96040f5b09baae56d0c3efeaa77d57602f69018f5cefd60cb5c71b6b6f8a4b0472e8740367266917d8c13', 16),
'e' => new Math_BigInteger('10001', 16)
));
echo $rsa;
Altho that said, if you're gonna use phpseclib to convert the key, why not use it to encrypt whatever it is that you're trying to encrypt as well?:
//$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
$rsa->encrypt('plaintext');
No, there isn't any way to do this. Public/private keys can only be used in asymmetric cryptography. openssl_encrypt() requires symmetric cryptography.
openssl_public_encrypt() which you need for asymmetric cryptography wants a pubic key resource. This can be obtained using openssl_pkey_get_public().
Now, openssl_pkey_get_public() needs a PEM file. The docs describe it as:
an X.509 certificate resource
a string having the format file://path/to/file.pem. The named file must contain a PEM encoded certificate/public key (it may contain both).
A PEM formatted public key.
So you need to create a PEM file from the public key configuration you have in your question. The modulus and exponent you have are enough to do this.
There are several examples on the internet on how to archive this. Have a look at:
Generate .pem RSA public key file using base 10 modulus and exponent?
rawrsa (https://github.com/JonathonReinhart/rawrsa)
how to convert raw modulus & exponent to RSA public key (.pem format)
Basically you need to create a "ASN1-formatted text file" and employ some openssl commands.
I am migrating an entire PHP API, and while I used PyCrypto before, I am not sure how to translate the following encryption call, since I need the exact same result. The PHP call is:
define('KEY', "-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC81t5iu5C0JxYq5/XNPiD5ol3Z
w8rw3LtFIUm7y3m8o8wv5qVnzGh6XwQ8LWypdkbBDKWZZrAUd3lybZOP7/82Nb1/
noYj8ixVRdbnYtbsSAbu9PxjB7a/7LCGKsugLkou74PJDadQweM88kzQOx/kzAyV
bS9gCCVUguHcq2vRRQIDAQAB
-----END PUBLIC KEY-----");
$cypher = "";
$result = openssl_public_encrypt($plain, $cypher, KEY, OPENSSL_PKCS1_PADDING);
echo bin2hex($cypher);
Assuming everything goes right, this prints the content from $cypher, passed to hexadecimal. For a sample input "azzzzzzzzzzzzdfdf" I get something like: "2281aeebc1166cdfb2f17a0a0775d927ca5a9ad999bae0e4954f58bd8082fdf7efe1fd284876530341f714456d7eb8cd44c57b20ab27029b84d5dc77a674bede3fe9065282931404286082e9df8607bdcff0818b90324dfee7d76b566d0f99bebc5cc913372c276ba373712128f1bcc226b59367cff93f7cdd6dbde25b366863".
I must assume this value as right, since the code was taken from an existing API I am migrating. However, trying the same with PyCrypto (yes, I am migrating the API to be available in Python), I use the following code:
def bin2hex(s):
return "".join([hex(ord(c))[2:].zfill(2) for c in s])
KEY = """-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC81t5iu5C0JxYq5/XNPiD5ol3Z
w8rw3LtFIUm7y3m8o8wv5qVnzGh6XwQ8LWypdkbBDKWZZrAUd3lybZOP7/82Nb1/
noYj8ixVRdbnYtbsSAbu9PxjB7a/7LCGKsugLkou74PJDadQweM88kzQOx/kzAyV
bS9gCCVUguHcq2vRRQIDAQAB
-----END PUBLIC KEY-----"""
from Crypto.Cipher import PKCS1_v1_5
from Crypto.PublicKey import RSA
encrypter = PKCS1_v1_5.new(RSA.importKey(KEY))
print bin2hex(encrypter.encrypt("azzzzzzzzzzzzdfdf"));
While I expect the same value be returned and printed, the value finally is "3dd94ffabd01bb0e94010c0fedbcd4eb648f12e5d9e6d934b77ae86f76681d8a1b790cad9fddf6e6720415b4d645e525c33c402fa9778739b8e461790387e9508f7158a5fdc5723f5fc26d166b11a00759f0e0ee3ba6719a2e7c6b918f66e1311d1fff878ee2ca8762e1d6120f1e9585a76cdc7719ca20129ae76182b4277170".
Using PKCS1_OAEP outputs "290f60f37088c2cb46ae9221b01ff46a463f270ef7cf70bbea49de0b5ae43aec34a0eb46e694cf22f689eb77e808c590fdc30eda09f9d3f3cb8c15e0505bf5a984c2a121bc9fa83c6b5ccf50235f072467b4ae9cdf0f0ee2e486626ffa62ad1aa715fbe29e8afe4ceab3ca5a5df4c1dc75d7f258285d7ff1f4f2b4dcb7a8413a".
It is easy to tell that I must fix my python code. How can I fix my python code so it returns the exact same result as the given PHP call?
Your code is fine. PKCS#1 v1.5 padding in pyCrypto is randomized (source). So the encryption will always be different even if you use the same key and plaintext. This is a desirable property.
If you want to check compatibility between pyCrypto and PHP's OpenSSL extension, then you would need to encrypt in one, decrypt in the other and check that you got what you expected.
PKCS#1 v1.5 padding should not be used nowadays, because there are efficient attacks against it. OAEP is a much better alternative.
I've struggled with the same problem for quite long but finally managed to solve this.
The main issue was setting proper SHA hashes, in case of PHP's openssl_public_encrypt() it should be SHA1.
Here's the code for openssl_public_encrypt() with OPENSSL_PKCS1_OAEP_PADDING:
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
def encrypt_key(message):
with open('public_key', 'rb') as key_file:
pub_key = key_file.read()
public_key = load_pem_public_key(pub_key, default_backend())
ciphertext = public_key.encrypt(
message,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA1()),
algorithm=hashes.SHA1(),
label=None
)
)
return ciphertext
basically a copy of cryptography.io documentation regarding RSA.
I'm having serious problems with what I was hoping would be a simple call to PHP's
openssl_public_encrypt();
Sadly, I get an invalid public key warning.
My goal is to simply RSA encrypt a users password using public key provided by their API which currently looks like this:
+Tir6+unMOaQ5tHqjjjwnlAMhccnCSMFEi3a0mhIxbW+O/GukjomGyzckQT2h0Ys70JezHbNq5YS3sYkNF29kCkz4HuNfy9eEjE/clA9/zyfT8ZcbnusLcLz2xNgbTp62fQdzBnReI5+dpj/N24krYvHaYIr8ACxDqBv2TR3E9M=AQAB
Apparently a working implementation using the same service I'm trying to use found a solution with these steps:
Extract the modulus and exponent from the public key
Re-constructs the key in the MS PUBLICKEYBLOB format
Call OpenSSL to convert the key to PEM format
Load the PEM public key from the converted file
Encrypts the password (after converting to UTF-16LE)
However not only that I dont know if its possible to do in PHP , I think there Must be an easier way!
One post I saw hinted that the exponent may come after the last = sign (so AQAB) but I don't know if this is reliable.
Something like this do what you want?:
<?php
$key = '+Tir6+unMOaQ5tHqjjjwnlAMhccnCSMFEi3a0mhIxbW+O/GukjomGyzckQT2h0Ys70JezHbNq5YS3sYkNF29kCkz4HuNfy9eEjE/clA9/zyfT8ZcbnusLcLz2xNgbTp62fQdzBnReI5+dpj/N24krYvHaYIr8ACxDqBv2TR3E9M=AQAB';
include('Crypt/RSA.php');
$rsa = new Crypt_RSA();
$rsa->loadKey(array(
'e' => new Math_BigInteger(65537),
'n' => new Math_BigInteger(substr($key, 0, -4), -256)
));
$ciphertext = $rsa->encrypt('password');
echo bin2hex($ciphertext);
?>
This example uses phpseclib, a pure PHP RSA implementation. Although phpseclib does not support the format of the key you posted it does support raw public keys and as such converting to PKCS1 style keys is unnecessar. And the ciphertext's produced by phpseclib are perfectly interoperable with OpenSSL.
OK, so what you need to do:
remove the AQAB part and use the value 65537 for the public exponent
base 64 decode the modulus
create a DER encoding of the public key in PKCS#1 format (see below)
base64 encode the DER encoding using 64 character line length and DOS line endings
add PEM header & footer
use the PEM encoded string to create a public key and finally
encrypt the password
The following ASN.1 spec defines the public key in PKCS#1 format:
RSAPublicKey ::= SEQUENCE {
modulus INTEGER, -- n
publicExponent INTEGER -- e
}
Now the identifier octet or tag of SEQUENCE is 30 in hex, INTEGER is 02 and the length should be specified like this. So you get something like:
30818902818100F938ABEBEBA730E690E6D1EA8E38F09E500C85C727092305122DDAD26848C5B5BE3BF1AE923A261B2CDC9104F687462CEF425ECC76CDAB9612DEC624345DBD902933E07B8D7F2F5E12313F72503DFF3C9F4FC65C6E7BAC2DC2F3DB13606D3A7AD9F41DCC19D1788E7E7698FF376E24AD8BC769822BF000B10EA06FD9347713D30203010001
in hexadecimals. So after base 64 encoding and adding the header and footer line you should get:
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQH5OKvr66cw5pDm0eqOOPCeUAyFxycJIwUSLdrSaEjFtb478a6SOiYbLNyRBPaHRizvQl7Mds2rlhLexiQ0Xb2QKTPge41/L14SMT9yUD3/PJ9Pxlxue6wt
wvPbE2BtOnrZ9B3MGdF4jn52mP83biSti8dpgivwALEOoG/ZNHcT0wIDAQAB
-----END RSA PUBLIC KEY-----
Happy coding.
I have an applet that uses a "foo.key" file and a string password to generate a privateKey object, using BouncyCastle apis. Then a signature is created using this privateKey object and a string such as "MYNAMEHERE". All I know is that the algorythm used to generate this signature is RSA.
What I want to do is to decrypt (or verify) this signature in PHP. I have both files publicKey (file.cer) and privateKey (file.key) that where used to generate the privateKey object in Java.
Im trying using the openssl_verify functions in PHP, passing the values:
openssl_verify("MYNAMEHERE", $signature, "file.cer"), where $signature contains the String representation of the signature object generated in Java: new String (signature).
I dont know if this process is correct to verify the signature, or what kind of encoding/decoding process i have to do before using this function.
I hope somebody points me the right direction!
Thanks in advance!
You haven't given enough information, such as the actual signature or how it is encoded. Normally RSA means RSA in PKCS#1 1.5 mode using SHA-1 (Google it) which is more or less the default signature generation/verification algorithm in use today. In that case, the verify should proceed as you've described. The password is not needed anymore, it might just be used to decrypt the private key. You can still use the private key to see if an sign in PHP/openssl does create the same data. If not, a different hash or PKCS#1 v2.1 signature may have been used.