I’m using a key pair generated by phpseclib and then I use it to encrypt in C# a message and decrypt in PHP.
The public key is:
-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvh4OvlS5+7Skk5Nij296 rDAKQsxZrz+gV/MInvWqe/SAOlOf8vqs6xJsn7sd523X8m+b0Kd751UbeeDQRGjC mbQc9iwQHUFrNa0bSVnpyoc27K+cuxNdtoPG1XXRCFLKKqVJKSyB5ZPu+oQr+OIo 6zYhU5jfCEYbpzPdyYqfDGJPpJv/r8F11MCBrdbvD5Z42ysTDqSxFGIfROWtDOP5 ShSNcVGRQYfX3E/v8j+qy0yu2PD/W1UqNd3m6TTkUj/u1XukUMWp+tfXEdEp7IEU askvGnBfHgJcsBp2c6kT9WxIU0ai4G8mo/2OsHprw42PBWLcJwfUeUgY3itSczni WQIDAQAB -----END PUBLIC KEY-----
The conversion to XML is done via https://superdry.apphb.com/tools/online-rsa-key-converter
Here is how the message is encrypted in C#:
public void Synch() { string messageToSend = "123"; var encryptedData = EncryptString(messageToSend); var request = new HTTPRequest(new Uri(APIUrl), HTTPMethods.Post, OnRequestFinished); request.AddField("message", encryptedData); request.FormUsage = BestHTTP.Forms.HTTPFormUsage.Multipart; request.Send(); } public byte[] EncryptBytes(string message) { // https://superdry.apphb.com/tools/online-rsa-key-converter string XML = "<RSAKeyValue><Modulus>vh4OvlS5+7Skk5Nij296rDAKQsxZrz+gV/MInvWqe/SAOlOf8vqs6xJsn7sd523X8m+b0Kd751UbeeDQRGjCmbQc9iwQHUFrNa0bSVnpyoc27K+cuxNdtoPG1XXRCFLKKqVJKSyB5ZPu+oQr+OIo6zYhU5jfCEYbpzPdyYqfDGJPpJv/r8F11MCBrdbvD5Z42ysTDqSxFGIfROWtDOP5ShSNcVGRQYfX3E/v8j+qy0yu2PD/W1UqNd3m6TTkUj/u1XukUMWp+tfXEdEp7IEUaskvGnBfHgJcsBp2c6kT9WxIU0ai4G8mo/2OsHprw42PBWLcJwfUeUgY3itSczniWQ==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>"; //get the object back from the stream RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); rsa.FromXmlString(XML); byte[] bytesMessage = Encoding.UTF8.GetBytes(message); var bytesCipherText = rsa.Encrypt(bytesMessage, false); return bytesCipherText; } public string EncryptString(string message) { string encryptedText = Convert.ToBase64String(EncryptBytes(message)); return encryptedText; }
What is sent (encrypted message) by C# to PHP (changes everytime):
DE9e82l/lP211YQ0gDhvIcwU1dIXaN7re9+VpUYEB62yRYYnmzZfHxv+WeMHtvg5Yq15Lr4rhvQpTiMGyktxkR5wPQRQhDX74gonPIKgYYIfoqF0mswkulaiLReagcroVyd4ZFEfPxKScLXqjo3yIFLaGWVMGoh81ZwRoOUEGLCPkhsRMp1CiHecGZEmZm7VNoiOEeA5xRdO3asl5/jrbnpXeNXcU4OFV4n1hfdw8W04VHyzDU3AmkDnpfO3AHnZluGPOYpl3OYVn9bsmuqL9d02gBr0UP72vp5GxNoDZAy3tzymhdNZArN9sfBXX+QPOlK2eaFwSypGIa/SUn+erg==
On the decryption side in PHP:
Route::post('/decrypt', function (Request $request) { $encoded_message = $request->input('message'); if (!empty($encoded_message)) { $private = RSA::loadFormat('PKCS8', file_get_contents(resource_path() . '/rsa/key.pem'), $password = false); try { $message = $private->decrypt(base64_decode($encoded_message)); echo $message; } } catch (Exception $e) { return response()->json([ 'message' => 'Failed to process: '.$e->getMessage().$e->getTraceAsString(), ]); }
The output is like this:
Response: {"message":"Failed to process: Decryption error#0 C:\www\laravel\vendor\phpseclib\phpseclib\phpseclib\Crypt\RSA\PrivateKey.php(469):
What am I missing here?
Advertisement
Answer
In the C# code PKCS#1 v1.5 padding is applied (2nd parameter of Encrypt()
is false
), phpseclib uses OAEPadding by default (here).
Decryption works if PKCS#1 v1.5 padding is explicitly specified in the PHP code:
$message = $private->withPadding(RSA::ENCRYPTION_PKCS1)->decrypt(base64_decode($encoded_message));
Alternatively, OAEPadding could be used in the C# code. But be careful, if in Encrypt()
the 2nd parameter is set to true
, .NET uses SHA-1 for both digests by default. However, phpseclib applies SHA-256 for both digests by default. Therefore, in the C# code, for compatibility with the PHP code, both digests must be explicitly set to SHA256 (this is not possible with RSACryptoServiceProvider
, but with RSACng
; also, the overload of Encrypt()
is needed, with which the digest can be set explicitly).