I need to encrypt a password on a web site using a public key. The public key is delivered through a Java web service with the information: The key is a RSA key to be used with RSA/ECB/PKCS1Padding algorithm. The public key is delivered as JSON in the form:
{
"kid":"PWD",
"kty":"RSA",
"use":"enc",
"n":"MTA0OTgzNjg0OTMxMzE2NjkwNTU4Mjg3NDIwMDg1NTY0ODEyMjg1MDk2NTcwNzU5NDIzNzM0O
DA3OTA2MzA0MDczNTU0NDQ2Njg3ODY2ODk2NTk0NjYzNTAxMzg0NzE1OTExMjA0MjU1MzMzOTIzMjA
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
zcwMjA3MzQxOTcwNzc4NDAwNzM3MTY2NDMyNzIwNjMwMDQwOTMwOTQ0MTA2OTE1NDEzMDAwNTMyMTE
5ODM0MTA2MjAzMDIyODEwMjYyMDM3MDQ0NzkxNzIzNTU1MjQyNjYxMzE2ODc4OTc5NzY1OTgzMjg4M
zQ0NDc3OTYwNTg3MzE2NTUwMDgx",
"e":"NjU1Mzc"
}
I tried to encrypt the password using the public key but the key generated is not correct. Here is my code:
encryptedPassword = EncrypIt("Password01", "MTA1MzQxNzIwODA3NjUwNzg5ND
Y4ODU2Mjc0NDA3ODIwMjQ1ODQ5NDE1MDk1MDIzMTM3MjM0NzAwNzYzNjc2MTgwNjg3ODMxMjA3
NTY0NTcxMjg2MzM4NjQ1NzEwMDcyMjY2MTQyNDIzMTczMjkwMDk0MTc0MTA5MTc5MzI0NjYwMjQ4NzI3NzM0MTQ5NDY0MjUwODkwO
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
c2OTAzNjc3NzQzODM3NzM0MjE2ODM0NjY4MjM4MTQ0OTA3MDQ3MTk1Njc1NzU3OTE2NjEyNzkzMzM2MzI3MDUyNjg0NDI5NDExNjI
2MzA0MzM5", "NjU1Mzc");
public static string EncrypIt(string password, string publicKey, string exponent)
{
UnicodeEncoding ByteConverter = new UnicodeEncoding();
byte[] publicKeyByte = ByteConverter.GetBytes(publicKey);
byte[] passwordByte = ByteConverter.GetBytes(password);
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
RSAParameters RSAKeyInfo = new RSAParameters();
RSAKeyInfo = RSA.ExportParameters(false); //Export only public key
//Set RSAKeyInfo to the public key values.
RSAKeyInfo.Modulus = publicKeyByte;
//RSAKeyInfo.Exponent = exponentBytes; //I tried to set exponent but I have an error
RSA.ImportParameters(RSAKeyInfo);
byte[] encryptedPassword = RSA.Encrypt(passwordByte, false);
return Convert.ToBase64String(encryptedPassword);
}
(The public key is different between the JSON and my code but don't pay attention to it, I just copied information from different sources)
The encrypted password I obtained is far too long: The encrypted password should be 172 characters (I know because I have a small Java program that allows me to encrypt passwords correctly) but I get 1100 characters.
I do not use the exponent: Should I ?
Could I not use the JSON to directly configure the RSACryptoServiceProvider correctly ?
The answer from owlstead helped me to get an encrypted password with the right string size but the service using the encrypted string reject it with the message: javax.crypto.IllegalBlockSizeException: Data must not be longer than 128 bytes
I otained the code of the java program wich is doing the correct encryption (see below). I need to achieve the same encryption using .NET.
public class EncryptionServiceImpl
{
private static final Charset UTF8 = Charset.forName("UTF-8");
#Resource(name = "briqueAuthentificationClient")
private BriqueAuthentificationClientImpl briqueAuthentificationClient;
protected static final String ALGORITHM_RSA = "RSA";
protected static final String TRANSFORMATION_RSA_ECB_PKCS1PADDING = "RSA/ECB/PKCS1Padding";
private static final Logger LOG = LoggerFactory.getLogger(EncryptionServiceImpl.class);
public EncryptionServiceImpl() {
LOG.info("constructeur EncryptionServiceImpl");
}
/**
* #param briqueAuthentificationClient the briqueAuthentificationClient to set
*/
public void setBriqueAuthentificationClient(final BriqueAuthentificationClientImpl briqueAuthentificationClient) {
this.briqueAuthentificationClient = briqueAuthentificationClient;
}
public String encrypt(final String input) throws GeneralSecurityException {
if (StringUtils.isNotBlank(input)) {
final CertificateDto certificate = this.briqueAuthentificationClient.getCurrentCertificate();
if (certificate != null) {
return new String(this.encryptAndEncode(input.getBytes(), certificate), EncryptionServiceImpl.UTF8);
} else {
throw new RuntimeException("Certificate is null");
}
}
return null;
}
protected byte[] encryptAndEncode(final byte[] input, final CertificateDto currentCertificate)
throws GeneralSecurityException {
// Création de la clé publique
final PublicKey publicKey = this.buildPublicKey(currentCertificate);
// Chiffre
final byte[] inputEncrypted = this.encrypte(input, publicKey);
// Encode
return this.encodeBase64Url(inputEncrypted);
}
protected PublicKey buildPublicKey(final CertificateDto currentCertificate) throws GeneralSecurityException {
if ("RSA".equals(currentCertificate.getKeyType())) {
return this.buildRSAPublicKey(currentCertificate);
}
LOG.error(String.format("Tentative de création d'une clé publique avec un algorithme non connu [%s]",
currentCertificate.getKeyType()));
return null;
}
protected PublicKey buildRSAPublicKey(final CertificateDto currentCertificate) throws GeneralSecurityException {
final BigInteger modulus = new BigInteger(new String(Base64.decodeBase64(currentCertificate.getModulus()),
EncryptionServiceImpl.UTF8));
final BigInteger publicExponent = new BigInteger(new String(Base64.decodeBase64(currentCertificate
.getPublicExponent()), EncryptionServiceImpl.UTF8));
try {
return KeyFactory.getInstance(ALGORITHM_RSA).generatePublic(new RSAPublicKeySpec(modulus, publicExponent));
} catch (InvalidKeySpecException e) {
throw e;
} catch (NoSuchAlgorithmException e) {
throw e;
}
}
protected byte[] encrypte(final byte[] input, final RSAPublicKeySpec rsaPublicKeySpec)
throws GeneralSecurityException {
PublicKey publicKey;
try {
publicKey = KeyFactory.getInstance(ALGORITHM_RSA).generatePublic(
new RSAPublicKeySpec(rsaPublicKeySpec.getModulus(), rsaPublicKeySpec.getPublicExponent()));
} catch (InvalidKeySpecException e) {
throw e;
} catch (NoSuchAlgorithmException e) {
throw e;
}
return this.encrypte(input, publicKey);
}
protected byte[] encrypte(final byte[] input, final PublicKey publicKey) throws GeneralSecurityException {
try {
final Cipher cipher = Cipher.getInstance(TRANSFORMATION_RSA_ECB_PKCS1PADDING);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(input);
} catch (NoSuchAlgorithmException e) {
throw e;
} catch (NoSuchPaddingException e) {
throw e;
} catch (IllegalBlockSizeException e) {
throw e;
} catch (BadPaddingException e) {
throw e;
}
}
protected byte[] decrypte(final byte[] input, final RSAPrivateKeySpec rsaPrivateKeySpec)
throws GeneralSecurityException {
final BigInteger modulus = rsaPrivateKeySpec.getModulus();
final BigInteger privateExponent = rsaPrivateKeySpec.getPrivateExponent();
try {
final PrivateKey privateKey = KeyFactory.getInstance(ALGORITHM_RSA).generatePrivate(
new RSAPrivateKeySpec(modulus, privateExponent));
final Cipher cipher = Cipher.getInstance(TRANSFORMATION_RSA_ECB_PKCS1PADDING);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(input);
} catch (NoSuchAlgorithmException e) {
throw e;
} catch (NoSuchPaddingException e) {
throw e;
} catch (IllegalBlockSizeException e) {
throw e;
} catch (BadPaddingException e) {
throw e;
} catch (InvalidKeySpecException e) {
throw e;
} catch (InvalidKeyException e) {
throw e;
}
}
protected byte[] encodeBase64Url(final byte[] input) {
return Base64.encodeBase64(input, false);
}
protected byte[] decodeBase64Url(final byte[] input) {
return Base64.decodeBase64(input);
}
/**
* Method to connect to an url
*
* #param httpclient the http connection
* #return the response GetMethod
* #throws OAuthException in cas of connection error
*/
private GetMethod connect(final HttpClient httpclient, final String url) {
final GetMethod httpget = new GetMethod(url);
try {
httpclient.executeMethod(httpget);
} catch (final UnknownHostException e) {
throw new RuntimeException("Connection ERROR - Host could not be determined.", e);
} catch (final IOException e) {
throw new RuntimeException("Connection ERROR - Input/Output error.", e);
}
return httpget;
}
}
The steps I accomplished with the help of owlstead are in the answer below.
When I use this Java program to encode the string Password01 I obtain a string like:
sy5/XElHvuYA4Rbq8OBydLymT82R+z77jy6MU2sNal7VenzPEbARgy7p3zWgYgG13Cypk+zbnnB5L37fVUhgOgCqCyLtikzxJBNmPyzUK9+beiHJKyONZwifDzQ44hXTeXcZ0bmF9b5dLFy9QS/N5m28vIyBSGY8K2B7EB2FF38=
This encrypted password can be decrypted on Java side
When I use the .NET code the string is like:
ACZXYj/KudyxKBB510SxSouKaVwssmEUM6Jpreh8jTtrIH9eGb18GIdkBU7rXzMuLYbAhyREbFLbR87zW/2DNa4tN97FOlqr6k1JppJ/SSS/9fGdMvSOAQbWjsxksDH7fRu9dCvK0m0exFtGfXG7Yua9bB1m0dTNiwCZUZM0LnEM
This encrypted password failed to be decrypted on Java side. Failed with the error message:
javax.crypto.IllegalBlockSizeException: Data must not be longer than 128 bytes
You first need to perform base 64 decoding using Convert.FromBase64String of n and e, convert the result from ASCII encoding to a string and then parse the result using BigInteger.parse. Then you can convert using toByteArray to bytes, but beware that BigInteger is little endian, and RSAParameters expects big endian, so you have to reverse the bytes.
Your .NET RSA ciphertext (which you probably shouldn't reverse) is preceded by a 00h valued byte, which makes it an invalid ciphertext. RSA ciphertext must be of the exact length in bytes of the key.
Thank you sirs for the answers and comments, it help me to finally solve my issue:
For exponent, I got an error message: Invalid length for a Base-64 char array or string
This is because a base 64 value should be a multiple of 4. If this is not the case we should append equal sign (=) to reach a multiple of 4.
Thus after changing the exponent string from "NjU1Mzc" to "NjU1Mzc=" the value can be decoded.
Then I applied the solution provided by owlstead. Here is the final code which works fine:
//Decode from base 64
byte[] publicKeyByte = Convert.FromBase64String(rsaPublicKey.modulo);
byte[] exponentByte = Convert.FromBase64String(rsaPublicKey.exponent);
//Convert to ASCII string
UTF8Encoding ByteConverter = new UTF8Encoding();
string publicKeyString = System.Text.Encoding.Default.GetString(publicKeyByte);
string exponentString = System.Text.Encoding.Default.GetString(exponentByte);
//Convert to BigInteger
BigInteger publicKeyBI = BigInteger.Parse(publicKeyString);
BigInteger exponentBI = BigInteger.Parse(exponentString);
//Convert back to byte array
byte[] publicKeyByte2 = publicKeyBI.ToByteArray();
byte[] exponentByte2 = exponentBI.ToByteArray();
//We must remove the 129th sign byte which is added when converting to BigInteger
if (publicKeyByte2.Length == 129) Array.Resize(ref publicKeyByte2, 128);
//Create crypto service
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
RSAParameters RSAKeyInfo = new RSAParameters();
//Assign RSA key modulus/exponent reversing from little endian to big endian
RSAKeyInfo.Modulus = publicKeyByte2.Reverse().ToArray();
RSAKeyInfo.Exponent = exponentByte2.Reverse().ToArray();
RSA.ImportParameters(RSAKeyInfo);
//Convert password string to byte array
byte[] passwordByte = ByteConverter.GetBytes(clearPassword);
//Encrypt the password and encode 64
encryptedPassword = Convert.ToBase64String(RSA.Encrypt(passwordByte, false));
The missing point from owlstead is that: the method returns a byte array with an extra element whose value is zero to disambiguate positive values
See Microsoft documentation for more information on this point: BigInteger.ToByteArray Method
This code encrypts the password as a 172 characters string which ends with a = sign which is what I expected and it is correctly decrypted on Java side with the private key.
I tried this and it encrypts correctly
var input = "String to Encode.";
var mod = "MTA1MzQxNzIwODA3NjUwNzg5NDY4ODU2Mjc0NDA3ODIwMjQ1ODQ5NDE1MDk1MDIzMTM3MjM0NzAwNzYzNjc2MTgwNjg3ODMxMjA3NTY0NTcxMjg2MzM4NjQ1NzEwMDcyMjY2MTQyNDIzMTczMjkwMDk0MTc0MTA5MTc5MzI0NjYwMjQ4NzI3NzM0MTQ5NDY0MjUwODkwOXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXc2OTAzNjc3NzQzODM3NzM0MjE2ODM0NjY4MjM4MTQ0OTA3MDQ3MTk1Njc1NzU3OTE2NjEyNzkzMzM2MzI3MDUyNjg0NDI5NDExNjI2MzA0MzM5==";
var exp = "NjU1Mzc=";
var intValue = int.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(exp)));
var rsaParam = new RSAParameters();
rsaParam.Modulus = Convert.FromBase64String(mod);
rsaParam.Exponent = BitConverter.GetBytes(intValue);
using (var rsa = new RSACryptoServiceProvider())
{
rsa.ImportParameters(rsaParam);
Console.WriteLine(Convert.ToBase64String(rsa.Encrypt(Encoding.UTF8.GetBytes(input), false)));
}
Console.ReadLine();
I think the issue is the exponent is strange. Exp = 65537;
Related
I am trying to encrypt and decrypt my messages from my android app and been successful with the below code.
I want to decrypt this message in my c# based web application service. Can anyone please help me out with the equivalent code in c# for the below encryption code**.
this is the encrypting code
// encrypt the message
byte[] encryptedMsg = encryptSMS(secretKeyString,
msgContentString);
// convert the byte array to hex format in order for
// transmission
String msgString = byte2hex(encryptedMsg);
// send the message through SMS
sendSMS(recNumString, msgString);
// finish
finish();
} else
Toast.makeText(getBaseContext(),"Please enter phone number, secret key and the message. Secret key must be 16 characters!",Toast.LENGTH_SHORT).show();
}
});
}
public static void sendSMS(String recNumString, String encryptedMsg) {
try {
// get a SmsManager
SmsManager smsManager = SmsManager.getDefault();
// Message may exceed 160 characters
// need to divide the message into multiples
ArrayList<String> parts = smsManager.divideMessage(encryptedMsg);
smsManager.sendMultipartTextMessage(recNumString, null, parts,
null, null);
} catch (Exception e) {
e.printStackTrace();
}
}
// utility function
public static String byte2hex(byte[] b) {
String hs = "";
String stmp = "";
for (int n = 0; n < b.length; n++) {
stmp = Integer.toHexString(b[n] & 0xFF);
if (stmp.length() == 1)hs += ("0" + stmp);
else hs += stmp;
}
return hs.toUpperCase();
}
// encryption function
public static byte[] encryptSMS(String secretKeyString,
String msgContentString) {
try {
byte[] returnArray;
// generate AES secret key from user input
Key key = generateKey(secretKeyString);
// specify the cipher algorithm using AES
Cipher c = Cipher.getInstance("AES");
// specify the encryption mode
c.init(Cipher.ENCRYPT_MODE, key);
// encrypt
returnArray = c.doFinal(msgContentString.getBytes());
return returnArray;
} catch (Exception e) {
e.printStackTrace();
byte[] returnArray = null;
return returnArray;
}
}
private static Key generateKey(String secretKeyString) throws Exception {
Key key = new SecretKeySpec(secretKeyString.getBytes(), "AES");
return key;
}
}
and this is the decrypting code
// utility function: convert hex array to byte array
public static byte[] hex2byte(byte[] b) {
if ((b.length % 2) != 0) throw new IllegalArgumentException("hello");
byte[] b2 = new byte[b.length / 2];
for (int n = 0; n < b.length; n += 2) {
String item = new String(b, n, 2);
b2[n / 2] = (byte) Integer.parseInt(item, 16);
}
return b2;
}
// decryption function
public static byte[] decryptSMS(String secretKeyString, byte[] encryptedMsg)
throws Exception {
// generate AES secret key from the user input secret key
Key key = generateKey(secretKeyString);
// get the cipher algorithm for AES
Cipher c = Cipher.getInstance("AES");
// specify the decryption mode
c.init(Cipher.DECRYPT_MODE, key);
// decrypt the message
byte[] decValue = c.doFinal(encryptedMsg);
return decValue;
}
private static Key generateKey(String secretKeyString) throws Exception {
// generate AES secret key from a String
Key key = new SecretKeySpec(secretKeyString.getBytes(), "AES");
return key;
}
}
My question is pretty similar to the one form 2011, Signing and verifying signatures with RSA C#. Nevertheless, I also get false when I compare the signed data and the original message. Please point on my mistake.
Code:
public static void Main(string[] args)
{
//Generate a public/private key pair.
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
//Save the public key information to an RSAParameters structure.
RSAParameters RSAPublicKeyInfo = RSA.ExportParameters(false);
RSAParameters RSAPrivateKeyInfo = RSA.ExportParameters(true);
string message = "2017-04-10T09:37:35.351Z";
string signedMessage = SignData(message, RSAPrivateKeyInfo);
bool success = VerifyData(message, signedMessage, RSAPublicKeyInfo);
Console.WriteLine($"success {success}");
Console.ReadLine();
}
Signing method:
public static string SignData(string message, RSAParameters privateKey)
{
ASCIIEncoding byteConverter = new ASCIIEncoding();
byte[] signedBytes;
using (var rsa = new RSACryptoServiceProvider())
{
// Write the message to a byte array using ASCII as the encoding.
byte[] originalData = byteConverter.GetBytes(message);
try
{
// Import the private key used for signing the message
rsa.ImportParameters(privateKey);
// Sign the data, using SHA512 as the hashing algorithm
signedBytes = rsa.SignData(originalData, CryptoConfig.MapNameToOID("SHA512"));
}
catch (CryptographicException e)
{
Console.WriteLine(e.Message);
return null;
}
finally
{
// Set the keycontainer to be cleared when rsa is garbage collected.
rsa.PersistKeyInCsp = false;
}
}
// Convert the byte array back to a string message
return byteConverter.GetString(signedBytes);
}
Verification method:
public static bool VerifyData(string originalMessage, string signedMessage, RSAParameters publicKey)
{
bool success = false;
using (var rsa = new RSACryptoServiceProvider())
{
ASCIIEncoding byteConverter = new ASCIIEncoding();
byte[] bytesToVerify = byteConverter.GetBytes(originalMessage);
byte[] signedBytes = byteConverter.GetBytes(signedMessage);
try
{
rsa.ImportParameters(publicKey);
success = rsa.VerifyData(bytesToVerify, CryptoConfig.MapNameToOID("SHA512"), signedBytes);
}
catch (CryptographicException e)
{
Console.WriteLine(e.Message);
}
finally
{
rsa.PersistKeyInCsp = false;
}
}
return success;
}
Basically the problem is with string to byte[] encoding. I get the same problem with ASCIIEncoding and with UTF8Encoding.
Thank you in advance!
You cannot use ASCIIEncoding on the encoded message because it contains bytes which are invalid ASCII characters. The typical way you would store the encoded message is in a base64 string.
In SignData, use the following to encode the byte array into a string:
return Convert.ToBase64String(signedBytes);
and in VerifyData, use the following to decode the string back to the same byte array:
byte[] signedBytes = Convert.FromBase64String(signedMessage);
I'm trying to encrypt text in Android and decrypt it in a C# application. My problem is that the title's error appears when I'm decrypting.
Android (encryption) side:
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.Security;
import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.spec.SecretKeySpec;
import org.spongycastle.jce.provider.BouncyCastleProvider;
public class AesHelper {
private SecretKeySpec key;
private byte[] input;
private ByteArrayOutputStream output;
private CipherOutputStream cipherOutput;
private Cipher encrypt;
public AesHelper(byte[] chosenKey, String plaintext) {
Security.addProvider(new BouncyCastleProvider());
key = new SecretKeySpec(chosenKey, "AES");
try {
encrypt = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
encrypt.init(Cipher.ENCRYPT_MODE, key);
input = plaintext.getBytes();
} catch (Exception e) {
Log.d("testclient", e.getMessage());
}
}
public byte[] encrypt() {
output = new ByteArrayOutputStream();
cipherOutput = new CipherOutputStream(output, encrypt);
try {
cipherOutput.write(input);
cipherOutput.close();
} catch (IOException e) {
Log.d("testclient", e.getMessage());
}
return output.toByteArray();
}
}
Invoked as follows:
String message = "TEST-TEST-TEST-TEST-TEST-TEST-TEST-TEST";
cryptHelper = new AesHelper(key, message);
byte[] cipherMessage = cryptHelper.encrypt();
String finalMessage = bytesToHex(cipherMessage);
C# (decryption) side:
public class AesHelper
{
private readonly Encoding _encoding;
private readonly IBlockCipher _blockCipher;
private PaddedBufferedBlockCipher _cipher;
public IBlockCipherPadding Padding { get; set; }
public AesHelper()
{
_blockCipher = new AesEngine();
_encoding = Encoding.UTF8;
}
public string Encrypt(string plain, byte[] key)
{
var result = BouncyCastleCrypto(true, _encoding.GetBytes(plain), key);
return result.AsHex();
}
public string Decrypt(string cipher, byte[] key)
{
var result = BouncyCastleCrypto(false, cipher.AsByteArray(), key);
return _encoding.GetString(result);
}
private byte[] BouncyCastleCrypto(bool forEncrypt, byte[] input, byte[] key)
{
try
{
_cipher = Padding == null
? new PaddedBufferedBlockCipher(new CbcBlockCipher(_blockCipher))
: new PaddedBufferedBlockCipher(new CbcBlockCipher(_blockCipher), Padding);
_cipher.Init(forEncrypt, new KeyParameter(key));
return _cipher.DoFinal(input);
}
catch (CryptoException ex)
{
throw new CryptoException(ex.Message);
}
}
}
Invoked as follows:
cryptoHelper = new AesHelper { Padding = new Pkcs7Padding() };
var decryptedMessage = cryptoHelper.Decrypt(message, _aesKey);
The thing is, if I encrypt the string
"TEST-TEST-TEST-TEST-TEST-TEST-TEST-TEST"
the decrypted string is
"�\a��{ٳ�]%'Ts�EST-TEST-TEST-TEST-TEST"
I really can't figure out what I'm doing wrong.
It appears to be the first block of output (16 bytes) that is wrong, which for CBC mode implies a different "initialization vector" (IV) was used for encryption and decryption. The Android code is actually generating a (random) IV automatically (when you call Cipher.init() with just a key), which you could retrieve using Cipher.getIV(). Alternatively you can explicitly specify an IvParameterSpec using a different init method.
However you do it, that same IV needs to be available to the decryption code, and you would then init _cipher like this in the C# code:
_cipher.Init(forEncrypt, new ParametersWithIV(new KeyParameter(key), iv));
Also please note that in the Android code:
plaintext.getBytes()
might cause you problems later. It's better to explicitly say UTF8:
plaintext.getBytes("UTF8")
This is my code for decryption process:
private RSACryptoServiceProvider _rsa;
private string _privateKey;
private string _publicKey;
public RsaLibrary()
{
//initialsing the RSA object taking the option of a 1024 key size
_rsa = new RSACryptoServiceProvider(1024);
_privateKey = _rsa.ToXmlString(true);
_publicKey = _rsa.ToXmlString(false);
}
public string Decrypt(string ciphertext, string privateKey_ = null)
{
if (String.IsNullOrEmpty(privateKey_))
{
return DecryptToBytes(ciphertext, _privateKey);
}
else
{
return DecryptToBytes(ciphertext, privateKey_);
}
}
private string DecryptToBytes(string ciphertext, string privateKey)
{
if (String.IsNullOrEmpty(privateKey))
{
throw new ArgumentNullException("Error: No key provided.");
}
if (ciphertext.Length<=0)
{
throw new ArgumentNullException("Error: No message to decrypt.");
}
byte[] plaintext;
byte[] ciphertext_Bytes = Encoding.Unicode.GetBytes(ciphertext);
_rsa.FromXmlString(privateKey);
plaintext = _rsa.Decrypt(ciphertext_Bytes, false);
return Encoding.Unicode.GetString(plaintext);
}
The encryption code:
private string EncryptToByte(string plaintext, string publicKey)
{
if (String.IsNullOrEmpty(publicKey))
{
throw new ArgumentNullException("Error: No key provided.");
}
if (plaintext.Length<=0)
{
throw new ArgumentNullException("Error: No message to incrypt");
}
byte[] ciphertext;
byte[] plaintext_Bytes = Encoding.Unicode.GetBytes(plaintext);
_rsa.FromXmlString(publicKey);
ciphertext = _rsa.Encrypt(plaintext_Bytes, false);
return Convert.ToBase64String(ciphertext);
}
I can not see where I am going wrong. I have made sure that the keys are correct. The public one which i extracted using this line in the constructor:
_publicKey = _rsa.ToXmlString(false);
This public key is displayed on the form that I created. The private i used the "true" instead of false.
Any ideas?
Ciphertext is very unlikely to be genuinely UTF-16-encoded text. Assuming that the encryption side had something like:
string encryptedText = Encoding.Unicode.GetString(encryptedBytes);
you've basically lost data. The result of encryption is not text - it's arbitrary binary data. If you want to convert that to text for some transport reason, you should use Base64, e.g.
string base64EncryptedText = Convert.ToBase64String(encryptedBytes);
Then use Convert.FromBase64String to recover the original encrypted binary data which is ready to decrypt.
I recently posted about issues with encrypting large data with RSA, I am finally done with that and now I am moving on to implementing signing with a user's private key and verifying with the corresponding public key. However, whenever I compare the signed data and the original message I basically just get false returned. I am hoping some of your could see what I am doing wrong.
Here is the code:
public static string SignData(string message, RSAParameters privateKey)
{
//// The array to store the signed message in bytes
byte[] signedBytes;
using (var rsa = new RSACryptoServiceProvider())
{
//// Write the message to a byte array using UTF8 as the encoding.
var encoder = new UTF8Encoding();
byte[] originalData = encoder.GetBytes(message);
try
{
//// Import the private key used for signing the message
rsa.ImportParameters(privateKey);
//// Sign the data, using SHA512 as the hashing algorithm
signedBytes = rsa.SignData(originalData, CryptoConfig.MapNameToOID("SHA512"));
}
catch (CryptographicException e)
{
Console.WriteLine(e.Message);
return null;
}
finally
{
//// Set the keycontainer to be cleared when rsa is garbage collected.
rsa.PersistKeyInCsp = false;
}
}
//// Convert the a base64 string before returning
return Convert.ToBase64String(signedBytes);
}
So that is the first step, to sign the data, next I move on to verifying the data:
public static bool VerifyData(string originalMessage, string signedMessage, RSAParameters publicKey)
{
bool success = false;
using (var rsa = new RSACryptoServiceProvider())
{
byte[] bytesToVerify = Convert.FromBase64String(originalMessage);
byte[] signedBytes = Convert.FromBase64String(signedMessage);
try
{
rsa.ImportParameters(publicKey);
SHA512Managed Hash = new SHA512Managed();
byte[] hashedData = Hash.ComputeHash(signedBytes);
success = rsa.VerifyData(bytesToVerify, CryptoConfig.MapNameToOID("SHA512"), signedBytes);
}
catch (CryptographicException e)
{
Console.WriteLine(e.Message);
}
finally
{
rsa.PersistKeyInCsp = false;
}
}
return success;
}
And here is the test client:
public static void Main(string[] args)
{
PublicKeyInfrastructure pki = new PublicKeyInfrastructure();
Cryptograph crypto = new Cryptograph();
RSAParameters privateKey = crypto.GenerateKeys("email#email.com");
const string PlainText = "This is really sent by me, really!";
RSAParameters publicKey = crypto.GetPublicKey("email#email.com");
string encryptedText = Cryptograph.Encrypt(PlainText, publicKey);
Console.WriteLine("This is the encrypted Text:" + "\n " + encryptedText);
string decryptedText = Cryptograph.Decrypt(encryptedText, privateKey);
Console.WriteLine("This is the decrypted text: " + decryptedText);
string messageToSign = encryptedText;
string signedMessage = Cryptograph.SignData(messageToSign, privateKey);
//// Is this message really, really, REALLY sent by me?
bool success = Cryptograph.VerifyData(messageToSign, signedMessage, publicKey);
Console.WriteLine("Is this message really, really, REALLY sent by me? " + success);
}
Am I missing a step here? According to the Cryptography API and the examples there, I shouldn't manually compute any hashes, since I supply the algorithm within the method call itself.
Any help will be greatly appreciated.
Your problem is at the beginning of the VerifyData method:
public static bool VerifyData(string originalMessage, string signedMessage, RSAParameters publicKey)
{
bool success = false;
using (var rsa = new RSACryptoServiceProvider())
{
//Don't do this, do the same as you did in SignData:
//byte[] bytesToVerify = Convert.FromBase64String(originalMessage);
var encoder = new UTF8Encoding();
byte[] bytesToVerify = encoder.GetBytes(originalMessage);
byte[] signedBytes = Convert.FromBase64String(signedMessage);
try
...
For some reason you switched to FromBase64String instead of UTF8Encoding.GetBytes.