I need to decrypt a signature hash using RSA. I've got a hexadecimal string of 288 characters long which is a general public key from an institution. It represents hexadecimal bytes, so 144 bytes total.
The first 8 bytes are the so called CAR. Which is used for identification. The next 128 bytes are the Modulus N. And the next 8 bytes are the exponent E.
I've never worked with cryptography before so go easy on me. I'm using C# and the Bouncy Castle library for the decryption algorithms. Now, If I understand correctly, a 1024 bit modulus and 64 bits exponent is not strange. I currently have this bit of code:
public byte[] rsa_decrypt(byte[] data)
{
var N = PublicKey.ToCharArray().Slice(16,256);
var E = PublicKey.ToCharArray().Slice(272,16);
RsaEngine rsa = new RsaEngine();
Org.BouncyCastle.Math.BigInteger modulus = new Org.BouncyCastle.Math.BigInteger(new string(N).Insert(0,"00"),16);
Console.WriteLine(modulus);
Org.BouncyCastle.Math.BigInteger exponent = new Org.BouncyCastle.Math.BigInteger(new string(E).Insert(0,"00"),16);
Console.WriteLine(exponent);
RsaKeyParameters x = new RsaKeyParameters(false,modulus,exponent);
var eng = new Pkcs1Encoding(new RsaEngine());
eng.Init(false,x);
return eng.ProcessBlock(data,0,data.Length);
}
The Slice<T>(this T[],offset,length) method is just a small thing I wrote to cut arrays in pieces, nothing special and it works. The insertion of the "00" in the string is because the string could otherwise be interpreted as unsigned I believe.
When I run this code I get the exception
Unhandled exception. Org.BouncyCastle.Crypto.InvalidCipherTextException: block incorrect at Org.BouncyCastle.Crypto.Encodings.Pkcs1Encoding.DecodeBlock(Byte[] input, Int32 inOff, Int32 inLen) at Org.BouncyCastle.Crypto.Encodings.Pkcs1Encoding.ProcessBlock(Byte[] input, Int32 inOff, Int32 length)
Obviously I'm doing something wrong. Can anybody tell me what I'm doing wrong, why I'm doing it wrong, and most preferably, what I should be doing. Again, never worked with crypto algorithms or this library ever before.
Related
When porting a snippet of code from Java to C#, I have come across a specific function which I am struggling to find a solution to. Basically when decoding, an array of bytes from an EC PublicKey needs to be converted to a PublicKey object and everything I have found on the internet doesn't seem to help.
I am developing this on Xamarin.Android using Java.Security libraries and BouncyCastle on Mono 6.12.0.
This is the code I am using in Java:
static PublicKey getPublicKeyFromBytes(byte[] pubKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256r1");
KeyFactory kf = KeyFactory.getInstance("EC", new BouncyCastleProvider());
ECNamedCurveSpec params = new ECNamedCurveSpec("secp256r1", spec.getCurve(), spec.getG(), spec.getN());
ECPoint point = ECPointUtil.decodePoint(params.getCurve(), pubKey);
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, params);
return (ECPublicKey) kf.generatePublic(pubKeySpec);
}
This was the best solution I could come up with which didn't throw any errors in VS. Sadly, it throws an exception and tells me that the spec is wrong:
X9ECParameters curve = CustomNamedCurves.GetByName("secp256r1");
ECDomainParameters domain = new ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H);
ECPoint point = curve.Curve.DecodePoint(pubKey);
ECPublicKeyParameters pubKeySpec = new ECPublicKeyParameters(point, domain);
// Get the encoded representation of the public key
byte[] encodedKey = pubKeySpec.Q.GetEncoded();
// Create a KeyFactory object for EC keys
KeyFactory keyFactory = KeyFactory.GetInstance("EC");
// Generate a PublicKey object from the encoded key data
var pbKey = keyFactory.GeneratePublic(new X509EncodedKeySpec(encodedKey));
I have previously created a PrivateKey in a similar way where I generate a PrivateKey and then export the key in PKCS#8 format, then generating the object from this format. However I couldn't get this to work from an already set array of bytes.
Importing a raw public EC key (e.g. for secp256r1) is possible with pure Xamarin classes, BouncyCastle is not needed for this. The returned key can be used directly when generating the KeyAgreement:
using Java.Security.Spec;
using Java.Security;
using Java.Math;
using Java.Lang;
...
private IPublicKey GetPublicKeyFromBytes(byte[] rawXY) // assuming a valid raw key
{
int size = rawXY.Length / 2;
ECPoint q = new ECPoint(new BigInteger(1, rawXY[0..size]), new BigInteger(1, rawXY[size..]));
AlgorithmParameters algParams = AlgorithmParameters.GetInstance("EC");
algParams.Init(new ECGenParameterSpec("secp256r1"));
ECParameterSpec ecParamSpec = (ECParameterSpec)algParams.GetParameterSpec(Class.FromType(typeof(ECParameterSpec)));
KeyFactory keyFactory = KeyFactory.GetInstance("EC");
return keyFactory.GeneratePublic(new ECPublicKeySpec(q, ecParamSpec));
}
In the above example rawXY is the concatenation of the x and y coordinates of the public key. For secp256r1, both coordinates are 32 bytes each, so the total raw key is 64 bytes.
However, the Java reference code does not import raw keys, but an uncompressed or compressed EC key. The uncompressed key corresponds to the concatenation of x and y coordinate (i.e. the raw key) plus an additional leading 0x04 byte, the compressed key consists of the x coordinate plus a leading 0x02 (for even y) or 0x03 (for odd y) byte.
For secp256r1 the uncompressed key is 65 bytes, the compressed key 33 bytes. A compressed key can be converted to an uncompressed key using BouncyCastle. An uncompressed key is converted to a raw key by removing the leading 0x04 byte.
To apply the above import in the case of an uncompressed or compressed key, it is necessary to convert it to a raw key, which can be done with BouncyCastle, e.g. as follows:
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Crypto.EC;
...
private byte[] ConvertToRaw(byte[] data) // assuming a valid uncompressed (leading 0x04) or compressed (leading 0x02 or 0x03) key
{
if (data[0] != 4)
{
X9ECParameters curve = CustomNamedCurves.GetByName("secp256r1");
Org.BouncyCastle.Math.EC.ECPoint point = curve.Curve.DecodePoint(data).Normalize();
data = point.GetEncoded(false);
}
return data[1..];
}
Test: Import of a compressed key:
using Java.Util;
using Hex = Org.BouncyCastle.Utilities.Encoders.Hex;
...
byte[] compressed = Hex.Decode("023291D3F8734A33BCE3871D236431F2CD09646CB574C64D07FD3168EA07D3DB78");
pubKey = GetPublicKeyFromBytes(ConvertToRaw(compressed));
Console.WriteLine(Base64.GetEncoder().EncodeToString(pubKey.GetEncoded())); // MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMpHT+HNKM7zjhx0jZDHyzQlkbLV0xk0H/TFo6gfT23ish58blPNhYrFI51Q/czvkAwCtLZz/6s1n/M8aA9L1Vg==
As can be easily verified with an ASN.1 parser (e.g. https://lapo.it/asn1js/), the exported X.509/SPKI key MFkw... contains the raw key, i.e. the compressed key was imported correctly.
I wanna ask question about RSA sign on C#.
I have one document for sign. I have plain hex values of modulus, private exponent and public exponent. I dont' t want to signing with random generated keys. When I try with sign with sign tool, i have got result perfectly. But i can' t do it with C#. I am new for this kind of problem. So i am really stucked.
I looked a lot of sites and arcticles but i couldn't find any solution. Maybe there is but i can' t get it, i don' t know.
If you could explain to me with details(step-step), i will really be grateful.
In .NET you need the full set of CRT parameters for the private key, so if you have D you'll also need P, Q, DP, DQ, and InverseQ.
RSAParameters rsaParams = new RSAParameters();
rsaParams.Modulus = ParseHex(modulus);
rsaParams.Exponent = ParseHex(exponent);
rsaParams.D = ParseHex(d, rsaParams.Modulus.Length);
rsaParams.P = ParseHex(p, (rsaParams.Modulus.Length + 1) / 2);
rsaParams.Q = ParseHex(q, rsaParams.P.Length);
rsaParams.DP = ParseHex(dp, rsaParams.P.Length);
rsaParams.DQ = ParseHex(dq, rsaParams.P.Length);
rsaParams.InverseQ = ParseHex(inverseQ, rsaParams.P.Length);
RSA rsa = RSA.Create();
rsa.ImportParameters(rsaParams);
Where ParseHex(string str, int len=-1) is a routine which parses your hex strings to a fixed length array (if the hex string is short, the array should be left-padded with 0-byte values).
There is the function for sha256 computing:
static string GetHash(string input)
{
byte[] bytes = Encoding.UTF8.GetBytes(input); //1
SHA256 SHA256 = SHA256Managed.Create();
byte[] hashBytes = SHA256.ComputeHash(bytes); //2
var output = BitConverter.ToString(hashBytes); //3
return output;
}
It gets utf8-bytes from c# string, next it computes hash and returns one as string.
I'am confusing about BitConverter. Its ToString(byte[]) method depends on machine architecture (liitle/big endian). My purpose is providing specific endianess (big-endian) for output string.
How can I do it?
I think it can be like:
byte[] bytes = Encoding.UTF8.GetBytes(input); //1
if (BitConverter.IsLittleEndian)
{
Array.Reverse(bytes)
}
//..
But I dont' know how UTF8.GetBytes works (UTF8.GetBytes docs doesn't contains anything about endianness). Is it depending on endianness also? If so I suppose it is right way to reverse array after 1 step, is it?
I think it doesn't matter here because UTF-8 is byte oriented as stated here :
Isn’t on big endian machines UTF-8's byte order different than on little endian machines? So why then doesn’t UTF-8 require a BOM?
I'm tinkering with RSA signing of data.
I'm using a plaintext string, which i convert to byte array. i then generate private certificate, sign the byte array and then generate public key.
next i'm using the same byte array to verify the signature.
but i want to convert signature, in between steps, to the string - idea is to append it later on to the file that's being signed.
static void TestSigning(string privateKey)
{
string data = "TEST_TEST-TEST+test+TEst";
Console.WriteLine("==MESSAGE==");
Console.WriteLine(data);
byte[] dataByte = Encoding.Unicode.GetBytes(data);
using (var rsa = new RSACryptoServiceProvider())
{
rsa.FromXmlString(privateKey);
var publicKey = rsa.ToXmlString(false);
byte[] signature = rsa.SignData(dataByte, CryptoConfig.MapNameToOID("SHA512"));
string signatureString = Encoding.Unicode.GetString(signature);
byte[] roundtripSignature = Encoding.Unicode.GetBytes(signatureString);
Console.WriteLine("==TEST==");
Console.WriteLine(signature.Length.ToString());
Console.WriteLine(roundtripSignature.Length.ToString());
using (var checkRSA = new RSACryptoServiceProvider())
{
checkRSA.FromXmlString(publicKey);
bool verification = checkRSA.VerifyData(
dataByte,
CryptoConfig.MapNameToOID("SHA512"),
roundtripSignature);
Console.WriteLine("==Verification==");
Console.WriteLine(verification.ToString());
Console.ReadKey();
}
}
}
now here's the fun part
if i use UTF8 encoding i get byte arrays of different length
256 is the original size
484 is the roundtrip
UTF7 returns different sizes too
256 vs 679
both ASCII and Unicode return proper sizes 256 vs 256.
i've tried using
var sb = new StringBuilder();
for (int i = 0; i < signature.Length; i++)
{
sb.Append(signature[i].ToString("x2"));
}
to get the string. I'm then using Encoding.UTF8.GetBytes() method
this time i get the sizes of:
256 vs 512
if i remove the format from toString() i get:
256 vs 670
signature verification alwayas failed.
it works fine if i use 'signature' instead of roundtripSignature.
my question: Why, despite using same encoding type i get different byte arrays and strings? shouldn't this conversion be lossless?
Unicode isn't a good choice because, at minimum, \0, CR, LF, <delete>, <backspace> (and the rest of the control codes) can mess things up. (See an answer about this for Encrypt/Decrypt for more).
As #JamesKPolk said, you need to use a suitable binary-to-text encoding. Base64 and hex/Base16 are the most common, but there are plenty of other viable choices.
I'm receiving a CryptographicException "Bad Hash.\r\n" from the code below when I call CreateSignature. Any ideas as to what might be causing this?
RSAPKCS1SignatureFormatter RSAFormatter =
new RSAPKCS1SignatureFormatter(new RSACryptoServiceProvider());
RSAFormatter.SetHashAlgorithm("SHA256");
byte[] signedHash = RSAFormatter.CreateSignature(myHash);
Your code snippet does not show how you get myHash but my guess is that it is not a 32 byte array. From MSDN:
The hash size for the SHA256 algorithm
is 256 bits.
Try defining your myHash like this: (Just an ugly sample here)
// 256 bit hash size
byte[] myHash = { 59,4,248,102,77,97,142,201,
210,12,224,93,25,41,100,197,
210,12,224,93,25,41,100,197,
213,134,130,135, 213,134,130,135};
When i ran your code with a hash of any other size i got the same exact error. Running with the array defined above, 256 bits or 32 bytes, it worked.
byte[] bPlainText;
bPlainText = Encoding.UTF8.GetBytes(PlainText);
SHA256 sha256 = new SHA256Managed();
bPlainText=sha256.ComputeHash(bPlainText);