I am getting an error decrypting a message in go that was encrypted in C# (using corresponding public/private keys)
My client is written in C# and my server is written in Go. I generated a private and public key via go's crypto/rsa package (using rsa.GenerateKey(random Reader, bits int)). I then store the public key file generated where the client can access it and the private key where the server can access it. I encrypt on the client with the following code (using bouncy castle):
public static string Encrypt(string plainText)
{
byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
PemReader pr = new PemReader(
new StringReader(m_publicKey)
);
RsaKeyParameters keys = (RsaKeyParameters)pr.ReadObject();
// PKCS1 OAEP paddings
OaepEncoding eng = new OaepEncoding(new RsaEngine());
eng.Init(true, keys);
int length = plainTextBytes.Length;
int blockSize = eng.GetInputBlockSize();
List<byte> cipherTextBytes = new List<byte>();
for (int chunkPosition = 0; chunkPosition < length; chunkPosition += blockSize)
{
int chunkSize = Math.Min(blockSize, length - chunkPosition);
cipherTextBytes.AddRange(eng.ProcessBlock(
plainTextBytes, chunkPosition, chunkSize
));
}
return Convert.ToBase64String(cipherTextBytes.ToArray());
}
The go server parses this string from the header and uses the private key to decrypt:
func DecryptWithPrivateKey(ciphertext []byte, priv *rsa.PrivateKey) []byte {
hash := sha512.New()
plaintext, err := rsa.DecryptOAEP(hash, rand.Reader, priv, ciphertext, nil)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
}
return plaintext
}
The decryption function throws crypto/rsa: decryption error. If I try pasting the cipher text directly into go (rather then sending from the client), the same error occurs.
NOTE: in order to get the public key to load, I needed to change the header from:
-----BEGIN RSA PUBLIC KEY-----
...
to
-----BEGIN PUBLIC KEY-----
...
and the same for the footer. I am assuming this is a formatting issue but not sure how to go about solving.
EDIT: it seems that golang OAEP uses sha256 and bouncy castle uses SHA-1. Go's documentation specifies that the hash for encryption and decryption must be the same. This seems likely to be the issue? If it is, how can I change the hashing algorithm used by either go or C#?
Yes, you need to match the hash. In GoLang you've already set it to SHA-512 if I take a look at your code. Using SHA-256 at minimum should probably be preferred, but using SHA-1 is relatively safe as the MGF1 function doesn't rely on the collision resistance of the underlying hash. It's also the default for most runtimes, I don't know why GoLang decided against that.
Probably the best is to set SHA-512 for both runtimes, so here is the necessary constant for .NET.
Note that the underlying story is even more complex as OAEP uses a hash over a label as well as a hash within MGF1 (mask generation function 1, the only one specified). Both need to be specified in advance and generally the same hash function is used, but sometimes it is not.
The label is generally empty and most runtimes don't even allow setting it, so the hash value over the label is basically a hash-function specific constant that doesn't matter for security. The constant just manages to make things incompatible; "More flexible" isn't always a good thing.
Related
I want to verify a private key signed SHA256 hash using the CryptographicEngine in a UWP application. The hash is created externally and is signed with a private RSA key with passphrase. For this example however, I also generate the unsigned hash. Both hashes are then compared at the end to verify that they are the same.
I have created my private and public keys using OSX command line, specified in this blog.
This gave me two .pem files. My public key has the following structure:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3fasaNKpXDf4B4ObQ76X
qOaSRaedFCAHvsW4G0PzxL/...ETC ETC
-----END PUBLIC KEY-----
Here is my C# code to decrypt the hash:
//HASH THE INPUT STRING
var inputText = "stringtohash";
// put the string in a buffer, UTF-8 encoded...
IBuffer input = CryptographicBuffer.ConvertStringToBinary(inputText,
BinaryStringEncoding.Utf8);
// hash it...
var hasher = HashAlgorithmProvider.OpenAlgorithm("SHA256");
IBuffer hashed = hasher.HashData(input);
// format it...
string ourhash = CryptographicBuffer.EncodeToBase64String(hashed);
Debug.WriteLine(ourhash);
//CONVERT EXTERNAL HASH TO BUFFER
IBuffer data = CryptographicBuffer.DecodeFromBase64String("b18fbf9bc0fc7595af646155e18b71e1aeccf01719f9f293c72217d7b95cc2106edb419078c4c5c1c7f7d106b90198a4f26beb49ff4a714db4bface1f94fff193b8126ce05fe13825144a3dde97f55399846b6fd768f1fb152f1ba71bbf5cde8c1a7e58621a493070256e2444db36c346a88e870906529cf13c072ead50b6a01b2e74c7ef8c5d423e8ea25220f524b563ae2c3345b7837f9cd1a357540b1380c86287b9a240cf67f7518f11418352b665b657c5ffb6cbcb6126ec59e360de6304392b78cf4de79b52d73b8292df6a1e643d0c0f0945aae5949b391e2915772c996f03e6d1879192b7edf0f40c01b875e768358aa47a992070f628418ddf06472");
//CONVERT PUBLIC KEY TO BUFFER
IBuffer publickey = CryptographicBuffer.DecodeFromBase64String("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3fasaNKpXDf4B4ObQ76XqOaSRaedFCAHvsW4G0PzxL / RuAQFz80esZPyyDCps1PAbTKzQ + QblChPo7PJkbsU4HzNN4PIRGh5xum6SRmdvOowrlTUtyxdOkRJoFxmiR / VCea + PUspt26F7PLcK9ao5 + hVzMvPuqdYenqzd01f1t5hQEhFQ9qjB6Es8fpizHd / RSRfZ7n6rVKm9wYfCRLB7GJ7IHhWGuZrx9fjzsbW8eagu06qRhnUuR5oDVjXC8ZeazsRiw50xMuOzkhX9Oo081IYikwCgseJmQhT7vF4lZoyeB4qJpwTCA + glSy1w9N8ZfxyXK8QaT2RsrBrzl0ZCwIDAQAB");
// Open an asymmetric algorithm provider for the specified algorithm.
AsymmetricKeyAlgorithmProvider rsa = AsymmetricKeyAlgorithmProvider.OpenAlgorithm(AsymmetricAlgorithmNames.RsaPkcs1);
// Import Key
CryptographicKey key = rsa.ImportPublicKey(publickey, CryptographicPublicKeyBlobType.X509SubjectPublicKeyInfo);
// Decrypt the Hash using our Key
IBuffer result = CryptographicEngine.Decrypt(key, data, null);
Debug.WriteLine(result.ToString());
//Compare the two hashes
if (data == result) {
//Hash is verified!
}
Unfortunately when reaching the Decrypt method I get a NotImplementedException with error
The method or operation is not implemented
I researched online and I understand what needs to happen in theory but I don't know how to debug this further. What can I try?
Although both called PKCS#1 v1.5 padding, the padding for signature generation and encryption is not identical, see RFC 3447 for more details.
If you look at the RsaPkcs1 property you can see it is aimed at encryption:
Use the string retrieved by this property to set the asymmetric algorithm name when you call the OpenAlgorithm method. The string represents an RSA public key algorithm that uses PKCS1 to pad the plaintext. No hash algorithm is used.
As I don't see any option for "raw RSA", i.e. RSA without padding, it seems you are only able to verify your signature. However, RSA decryption expects an RSA private key. It's very likely that you get the error because of this: if you try and decrypt with a public key it will fail.
If you want to precompute the hash you can use VerifySignatureWithHashInput.
For other functionality you may have to use e.g. the C# lightweight API of Bouncy Castle. In the end you don't need platform provided cryptography to verify a signature.
I've been working on this for a while and I don't know much about either PHP or C#. We are building an application that is using AES 128 CBC mode encryption to store things in the database. One part is PHP and JS, the other is C# .NET WPF.
The person who wrote the PHP used the Mcrypt library to crypt/decrypt. I'm using the Chilkat library to encrypt/decrypt. Chilkat had a default C# example that is supposed to mimic the PHP Mcrypt.
Currently I can symmetrically encrypt/decrypt things on .Net, and .Net can decrypt anything from PHP. However the PHP side cannot decrypt anything that I encrypt to the database from the .Net side.
I've narrowed at least part of down to encoding issues, but I'm not sure how to fix it. The decryption scheme on PHP side usually decrypts to ASCII, but for things that I send it decrypts to UTF-8. I've tried to decrypt it then encode from UTF-8 to ASCII to no avail.
I'll show you the in/outputs and the functions. The IV is being set to 16 ASCII 0s to help my debugging along even though it shouldn't really matter.
input from .Net to mcrypt_encrypt func: string "1220"
output: 3tRIG7qUxUsU7WoXDybRRcdQRobOfeFGtQ438V7XRD8=
Parameter input into database = 'same as above'
input of PHP side decrypt func = 3tRIG7qUxUsU7WoXDybRRcdQRobOfeFGtQ438V7XRD8= 'same as above'
output of the mcrypt_decrypt function = ��J���{$�Z'?�u 'iconv says utf-8 encoding'
Ask for anything else and I'll get it if it would help. I'm sure this is some stupid easy problem I can't see.
PHP side - if it matters the PHP charset is set to UTF-8
function encrypt($input)
{
$this->iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
/*No longer using random iv :(
$this->iv = mcrypt_create_iv($this->iv_size, MCRYPT_RAND);*/
//use 16 zeros
$this->iv = '0000000000000000';
$encrypted = $this->iv .mcrypt_encrypt(MCRYPT_RIJNDAEL_128, KEY, $input, MODE, $this->iv);
//Finally encode this as base 64, see http://php.net/manual/en/function.base64-encode.php
$encrypted = base64_encode($encrypted);
return $encrypted;
}
function decrypt($input)
{
/*Get our message back!!!
First decode the base 64 string. Note, de/encoding bas 64 != encryption*/
$ciphertext_dec = base64_decode($input);
//Get the iv back out for decryption
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
//$iv_dec = substr($ciphertext_dec, 0, $iv_size);*/
$iv_dec = '0000000000000000';
//Now get the text of encrypted message (all but the iv in front)
$ciphertext_dec = substr($ciphertext_dec, $iv_size);
//Now decrypt the message
$plaintext_dec = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, KEY, $ciphertext_dec, MCRYPT_MODE_CBC, $iv_dec);
//Test
//test
//$plaintext_dec = iconv("UTF-8", "ASCII", $plaintext_dec);
//echo mb_detect_encoding($plaintext_dec, "auto");
//echo $plaintext_dec;
/*However, we might now have blank space # end of output b/c
remember we de/encrypt via block, so a 10 char long message
could be padded to 16 char long with blank spaces. Get rid of those.*/
$plaintext_dec = trim($plaintext_dec);
//return so we can compare to, i.e., original input
return $plaintext_dec;
}
.NET C#
public string mcrypt_encrypt(string plainText)
{
plainText = Encoding.ASCII.GetString(crypt.IV) + plainText;
byte[] myText = Encoding.ASCII.GetBytes(plainText);
// Do 128-bit AES encryption:
byte[] cipherText = crypt.EncryptBytes(myText);
return Convert.ToBase64String(cipherText);
}
public string mcrypt_decrypt(string cipher_text)
{
byte[] cipher_dec = Convert.FromBase64String(cipher_text);
byte[] plainBytes = crypt.DecryptBytes(cipher_dec);
string decrypted = Encoding.ASCII.GetString(plainBytes);
string plain_text = decrypted.Substring(16, decrypted.Length - 16);
return plain_text.TrimEnd('\0');
}
C# Chilkat init:
// AES is also known as Rijndael.
crypt.CryptAlgorithm = "aes";
// CipherMode may be "ecb" or "cbc"
crypt.CipherMode = "cbc";
// KeyLength may be 128, 192, 256
crypt.KeyLength = 128;
// Pad with NULL bytes (PHP pads with NULL bytes)
crypt.PaddingScheme = 3;
// EncodingMode specifies the encoding of the output for
// encryption, and the input for decryption.
// It may be "hex", "url", "base64", or "quoted-printable".
crypt.EncodingMode = "hex";
// The secret key must equal the size of the key. For
// 256-bit encryption, the binary secret key is 32 bytes.
// For 128-bit encryption, the binary secret key is 16 bytes.
string keyAscii = #"&=*FS6wksG#Zs3qG";
crypt.SecretKey = Encoding.UTF8.GetBytes(keyAscii);
crypt.Charset = "ASCII";
crypt.SetEncodedIV("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", "ascii");
You have a variety of different problems here. The issue causing the problem you seeing in not being able to decrypt in PHP data that was encrypted in .NET is that in the PHP version you're performing a substr on the ciphertext prior to decryption. The comments in the code indicate that you're removing the IV, except that that seems to have only been relevant in a prior version of your code when you were (correctly) using a random IV each time - now you're just discarding the first 16 bytes of ciphertext which due to the mode of operation corrupts the subsequent data block.
The other problem (though masked by the fact that you're discarding the first 16 bytes of the plaintext data when decrypting in .NET) is that the IV you're using in .NET (16 bytes of 0x00) is not the same as the IV you're using in PHP (16 '0' characters = 16 bytes of 0x30).
I would suggest reverting to using a random IV for every encryption and prepending the IV to the ciphertext after encryption When decrypting, read the IV from the first bytes of the ciphertext then decrypt the remainder. This is much more secure than having a static IV, especially when the data being encrypted is likely to often be the same.
It looks like it's a simple character encoding issue. Your C# code is getting the ASCII representation of your string and encrypting that, but your PHP code is decrypting it and expecting it to be UTF-8.
Try swapping your Encoding.ASCII calls for Encoding.UTF8 and make sure your crypt.Charset and crypt.SetEncodedIV are UTF8 as well
I have RSA modulus and exponent, i want to generate a public key with this components. Then i want to encrypt a data with this public key.
So i wrote this function:
public static byte[] EncryptRSA(byte[] rsaModulus, byte[] exponent, byte[] data)
{
byte[] response = null;
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
RSAParameters rsaPar = rsa.ExportParameters(false);
rsaPar.Modulus = rsaModulus;
rsaPar.Exponent = exponent;
rsa.ImportParameters(rsaPar);
response = rsa.Encrypt(data, false);
return response;
}
but rsa.ExportParameters method takes long time.
public RSACryptoServiceProvider ()
: this (1024)
{
// Here it's not clear if we need to generate a keypair
// (note: MS implementation generates a keypair in this case).
// However we:
// (a) often use this constructor to import an existing keypair.
// (b) take a LOT of time to generate the RSA keypair
// So we'll generate the keypair only when (and if) it's being
// used (or exported). This should save us a lot of time (at
// least in the unit tests).
}
As you can see ExportParameters() method is performing RSA key-pair generation which is time consuming operation.
After that i get exception "Private/public key mismatch" at importing RSA parameters.
Simply replace the export with creating a new object:
RSAParameters rsaPar = rsa.ExportParameters(false);
with
RSAParameters rsaPar = new RSAParameters();
That should still be slow in .net but should be fast in mono since it creates keys lazily.
I also strongly recommend using OAEP padding instead PKCS#1v1.5 padding. The latter has weaknesses that can be exploited in practice unless you carefully work around them. So use rsa.Encrypt(data, true) not rsa.Encrypt(data, false).
I don't see any clear way of removing the time-out related to generating the key pair. You are probably better off using FromXMLString as I don't see any method to generate the RSAParameters object an other way.
As for the mismatch, that is to be expected - the private key is still in there. Microsoft uses a second RSACryptoServiceProvider (check the sample code) to get around this issue.
I am trying to port the following code from C# into Java. I have made multiple attempts to try and decrypt my encrypted data and I get gibberish every time. The code below uses the org.bouncycastle library and unfortunately there doesn't seem to be a 1-1 mapping between the C# code and the Java code.
I basically know three things:
byte[] file - This contains my encrypted file. Usually a pretty large array of bytes.
byte[] padding - It is 32*bytes* every time and it seems that the first 16 bytes of this are used as the IV.
byte[] aesKey - It is 32*bytes* every time and I do not know how exactly the C# code is using this array.
Original C# Code
private byte[] decryptmessage(byte[] cmessage, byte[] iVector, byte[] m_Key)
{
{
//// randomly generated number acts as inetialization vector
m_IV = new byte[16];
Array.Copy(iVector, 0, m_IV, 0, 16);
// GenerateAESKey();
KeyParameter aesKeyParam = ParameterUtilities.CreateKeyParameter("AES", m_Key);
ParametersWithIV aesIVKeyParam = new ParametersWithIV(aesKeyParam, m_IV);
IBufferedCipher cipher = CipherUtilities.GetCipher("AES/CFB/NoPadding");
cipher.Init(false, aesIVKeyParam);
return cipher.DoFinal(cmessage);
}
}
My attempt in Java
private static byte[] decryptMessage(byte[] file, byte[] iVector, byte[] aesKey) throws Exception {
IvParameterSpec spec = new IvParameterSpec(Arrays.copyOfRange(iVector, 0, 16));
SecretKeySpec key = new SecretKeySpec(Arrays.copyOfRange(aesKey, 0, 16), "AES");
Cipher cipher = Cipher.getInstance("AES/CFB/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, key, spec);
return cipher.doFinal(file);
}
P.S: This is the final step of decryption. Before all this I had to take out some initial set of bytes from my encrypted file and decrypt them using an RSA private key to get this AES key.
If someone has a link / document I can read that properly explains the whole process of using AES to encrypt a file, then using RSA on the key and iv to the begining of the encrypted file, I will be extremely happy. I have just been staring at the C# code, I'd like to see something with pictures.
EDIT: Bytes not bits.
EDIT2: Renamed padding to iVector for consistency and correctness.
In the C# code, you initialize the key with 256 bits (32 bytes) and thus get AES-256. In the Java code, you only use 128 bit (16 bytes) and get AES-128.
So the fix is probably:
SecretKeySpec key = new SecretKeySpec(aesKey, "AES");
You might then find that Java doesn't want to use 256 bit keys (for legal reason). You then have to intall the Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files 6.
I'm trying to port the following Java code to a C# equivalent:
public static String encrypt(String value, String key) throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
byte[] bytes = value.getBytes(Charset.forName("UTF-8"));
X509EncodedKeySpec x509 = new X509EncodedKeySpec(DatatypeConverter.parseBase64Binary(key));
KeyFactory factory = KeyFactory.getInstance("RSA");
PublicKey publicKey = factory.generatePublic(x509);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
bytes = cipher.doFinal(bytes);
return DatatypeConverter.printBase64Binary(bytes);
}
So far I managed to write the following in C#, using the BouncyCastle library for .NET:
public static string Encrypt(string value, string key)
{
var bytes = Encoding.UTF8.GetBytes(value);
var publicKeyBytes = Convert.FromBase64String(key);
var asymmetricKeyParameter = PublicKeyFactory.CreateKey(publicKeyBytes);
var rsaKeyParameters = (RsaKeyParameters) asymmetricKeyParameter;
var cipher = CipherUtilities.GetCipher("RSA");
cipher.Init(true, rsaKeyParameters);
var processBlock = cipher.DoFinal(bytes);
return Convert.ToBase64String(processBlock);
}
The two methods, though, produce different results even if called with the same parameters.
For testing purposes, I'm using the following public RSA key:
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCLCZahTj/oz8mL6xsIfnX399Gt6bh8rDHx2ItTMjUhQrE/9kGznP5PVP19vFkQjHhcBBJ0Xi1C1wPWMKMfBsnCPwKTF/g4yga6yw26awEy4rvfjTCuFUsrShSPOz9OxwJ4t0ZIjuKxTRCDVUO7d/GZh2r7lx4zJCxACuHci0DvTQIDAQAB
Could you please help me to port the Java code successfully or suggest an alternative to get the same result in C#?
EDIT1: output in Java is different each time I run the program. I don't think that any padding was specified, so I don't understand what makes the output random.
EDIT2: Java uses PKCS1 by default, so it was enough to specify it in the C# cipher initialization to get the same encryption type (although not the same result, which was irrelevant at this point).
As an educated guess, I would say that Java adds random padding to create a stronger encryption.
Most practical implementations of RSA do this, and as the wiki puts it...
Because RSA encryption is a deterministic encryption algorithm – i.e., has no random component – an attacker can successfully launch a chosen plaintext attack against the cryptosystem, by encrypting likely plaintexts under the public key and test if they are equal to the ciphertext. A cryptosystem is called semantically secure if an attacker cannot distinguish two encryptions from each other even if the attacker knows (or has chosen) the corresponding plaintexts. As described above, RSA without padding is not semantically secure.
This is likely why your two methods don't output the same.