Retriving RSA public key from hex string generated from x509certificate getpublickeystring method - c#

I've been developing a project involving digital signatures. I'm given a publickey generated through getpublickeystring method of x509Certificate2.
This is the given publickey
3082010A0282010100D8F165280738A827AA1D2960D09ECA7EA3928BC3B6BA4CF034BEFE6E00DB434793B96C2FD2411BE086B6CE4B30441D18DFF377497F99F043E74B2C30555D2157C2838C9E7BDD566F0633CC0BE6F2649278BB8FE15EB2D4020C087B2252D8ED024DD260A3AA07F399D90EBDA722BEFECAD6D7A011A30F9013C4AFE3E15BA5E5B9028DE51079CDE125555B82E4269699E662D8BF182DF173FCC90135FC117B8254BF7036FE0A9B0667C0266E8EF94F9EE630CA0CF611FF0BB2BBFD99A7B9D9CC6C93D272A7DC87FE59D35C34D9487F1038179C6576114994C942F826168BDF112DD17FC5725684AE74359FED8C753D1D7F01AB8F5A070E93DD89CA0C9BC2520C850203010001
But I'm unable to construct a correct pem certificate from this.
This the code I have used to generate pem from the hex string.
byte[] certBytes = Enumerable.Range(0, certHex.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(certHex.Substring(x, 2), 16))
.ToArray();
string certPem = "-----BEGIN RSA PUBLIC KEY-----\n" +
Convert.ToBase64String(certBytes, Base64FormattingOptions.InsertLineBreaks)
.TrimEnd() + "\n-----END RSA PUBLIC KEY-----\n";
Generated certPem
-----BEGIN CERTIFICATE-----
MIIBCgKCAQEA2PFlKAc4qCeqHSlg0J7KfqOSi8O2ukzwNL7+bgDbQ0eTuWwv0kEb4Ia2zkswRB0Y
3/N3SX+Z8EPnSywwVV0hV8KDjJ573VZvBjPMC+byZJJ4u4/hXrLUAgwIeyJS2O0CTdJgo6oH85nZ
Dr2nIr7+ytbXoBGjD5ATxK/j4Vul5bkCjeUQec3hJVVbguQmlpnmYti/GC3xc/zJATX8EXuCVL9w
Nv4KmwZnwCZujvlPnuYwygz2Ef8Lsrv9mae52cxsk9Jyp9yH/lnTXDTZSH8QOBecZXYRSZTJQvgm
FovfES3Rf8VyVoSudDWf7Yx1PR1/AauPWgcOk92JygybwlIMhQIDAQAB
-----END CERTIFICATE-----
But the result is failing when I use
X509Certificate2 cert = new X509Certificate2(Encoding.ASCII.GetBytes(certPem));
Going through some past questions I saw that rsa exponent and modulus values are separated using the, delimiter, but here, it is not present.
Kindly provide some insights to solve this issue.
Edit
Decryption Logic
csp = RSA.Create();
csp.ImportFromPem(certPem.ToCharArray());
var decryptedBytes = csp.Decrypt(encryptedData, RSAEncryptionPadding.Pkcs1);
Exception occurs during csp.Decrypt
Internal.Cryptography.CryptoThrowHelper.WindowsCryptographicException: 'Key does not exist.'

Related

Unable to generate public key from given mod and exp. Throws "Bad Data"

I wanted to know and understand the process to generate the JWE. I have given below details:
string mod = "2737"; // this is a 618 char long string constructed only with digits.
string exp = "65537";
string kid = "APIKEY.XX.665_Priv";
string keyEncAlgo = "RSA-OAEP";
string contentEncAlgo = "A256GCM";
And a payload in json format.
As with my limited knowledge in this field I proceeded with creating a public key using RSACryptoServiceProvider. And planned to use Jose.Jwt library.
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
RSAPaeameters rsaKeyInfo = new RSAPaeameters();
rsaKeyInfo.Modulus = Encode.ASCII.GetBytes(mod);
rsaKeyInfo.Exponent = Encode.ASCII.GetBytes(exp);
rsa.ImportParameters(rsaKeyInfo);
The last line in the above code rsa.ImportParameters(rsaKeyInfo) throws exception as bad data.
Although the same mod and exp being used in a Java app which eventually gets everything right. I am converting that Java code to C#. What I am doing here wrong. Or I have understood the process wrong.
Modulus (assuming that 2737 is merely the beginning) and exponent appear to be decimal representations (as string).
Under .NET5+ (and .NET Core 2.1+) these can be imported as follows:
using System.Numerics;
using System.Security.Cryptography;
...
string mod = "2737...";
string exp = "65537";
var rsaPublic = new RSACryptoServiceProvider();
rsaPublic.ImportParameters(new RSAParameters
{
Modulus = BigInteger.Parse(mod).ToByteArray(true, true),
Exponent = BigInteger.Parse(exp).ToByteArray(true, true)
});
Note that the byte arrays are unsigned (1st true) and with big endian byte order (2nd true).
An example of JWE with jose-jwt is:
using Jose;
using System.Collections.Generic;
...
IDictionary<string, object> addHeaders = new Dictionary<string, object>()
{
{ "kid", "APIKEY.XX.665_Priv" },
};
string payload = "some string";
string token = JWT.Encode(payload, rsaPublic, JweAlgorithm.RSA_OAEP, JweEncryption.A256GCM, null, addHeaders);
with the following header (if Base64 decoded):
{
"alg":"RSA-OAEP",
"enc":"A256GCM",
"kid":"APIKEY.XX.665_Priv"
}

How do I load an OpenSSL ECDSA key into C#?

I need to load an OpenSSL private key into a C# based application.
The commands I used to generate the key are:
$ openssl ecparam -name prime256v1 -genkey -noout -out eckey.pem
$ openssl ec -in eckey.pem
read EC key
writing EC key
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIMiuwhV+yI0od5E5pSU6ZGuUcflskYD4urONi1g3G7EPoAoGCCqGSM49
AwEHoUQDQgAEe+C/M6u171u5CcL2SQKuFEb+OIEibjw1rx+S5LK4gNNePlDV/bqu
Ofjwc5JDqXA07shbfHNIPUn6Hum7qdiUKg==
-----END EC PRIVATE KEY-----
openssl pkcs8 -topk8 -nocrypt -in eckey.pem -out ec2.pem
cat ec2.pem
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgyK7CFX7IjSh3kTml
JTpka5Rx+WyRgPi6s42LWDcbsQ+hRANCAAR74L8zq7XvW7kJwvZJAq4URv44gSJu
PDWvH5LksriA014+UNX9uq45+PBzkkOpcDTuyFt8c0g9Sfoe6bup2JQq
-----END PRIVATE KEY-----
The C# code I'm using
string privKeyPKCS8 = #"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgyK7CFX7IjSh3kTmlJTpka5Rx+WyRgPi6s42LWDcbsQ+hRANCAAR74L8zq7XvW7kJwvZJAq4URv44gSJuPDWvH5LksriA014+UNX9uq45+PBzkkOpcDTuyFt8c0g9Sfoe6bup2JQq";
byte[] privKeyBytes8 = Convert.FromBase64String(privKeyPKCS8);//Encoding.UTF8.GetBytes(privKeyEcc);
var pubCNG = CngKey.Import(privKeyBytes, CngKeyBlobFormat.EccPrivateBlob);
What is the correct way to load the EC based key into CngKey?
EDIT
The key within the base 64 encoding adheres to the following format:
ECPrivateKey ::= SEQUENCE {
version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
privateKey OCTET STRING,
parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
publicKey [1] BIT STRING OPTIONAL
}
Using the secp256r1 curve and a public key in uncompressed point format.
Your key PEM / ASCII armor (the header, footer and base 64) is encoded using the format described in RFC 5915: Elliptic Curve Private Key Structure. This was first specified by Standards for Efficient Cryptography Group (SECG) which is also where the named curve secp256r1 got it's name from. The curve is supported by Microsoft CNG.
ECPrivateKey ::= SEQUENCE {
version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
privateKey OCTET STRING,
parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
publicKey [1] BIT STRING OPTIONAL
}
You first need to convert this "raw" EC private key structure to a PKCS#8 structure using the command in the (updated) question:
openssl pkcs8 -topk8 -nocrypt -in eckey.pem -out ec2.pem
to get:
SEQUENCE(3 elem)
INTEGER 0 # version of PKCS#8 structure
SEQUENCE (2 elem)
OBJECT IDENTIFIER 1.2.840.10045.2.1 # it's an EC key)
OBJECT IDENTIFIER 1.2.840.10045.3.1.7 # it's secp256r1
OCTET STRING (1 elem) # the key structure
SEQUENCE (3 elem)
INTEGER 1 # version
OCTET STRING (32 byte) # private key value (removed)
[1] (1 elem)
BIT STRING (520 bit) # public key value (removed)
The resulting structure isn't that different, what you are seeing is actually the same as the initial structure. Except that the PKCS#8 structure has an Object Identifier (OID) for designating the key type and an OID for the curve itself in front while your key has just the OID afterwards as parameter. Both also carry the (optional) public key value in the BIT STRING.
So the decoder recognizes this type and returns the EC private key.
The EccPrivateBlob that you were using requires a Microsoft specific structure. See also my question here. It won't work with the structures mentioned above.

Convert C# to Ruby - HMAC SHA256 function

I'm trying to get the HMAC SHA256 value(str_signature), I followed the Ruby code from this post, although his example was converting code from Java(with a Hex key).
C#
string strRawSignature = "200123123891:12|11231231|GET|just%20test%20value"
// Convert signature to byte array in order to compute the hash
byte[] bSignature = Encoding.UTF8.GetBytes(strRawSignature);
// Convert ApiKey to byte array - for initializing HMACSHA256
byte[] bSecretKey = Convert.FromBase64String(strApiKey);
string strSignature = "";
using (HMACSHA256 hmac = new HMACSHA256(bSecretKey))
{
// Compute signature hash
byte[] bSignatureHash = hmac.ComputeHash(bSignature);
// Convert signature hash to Base64String for transmission
str_signature = Convert.ToBase64String(bSignatureHash);
}
Ruby
require "openssl"
require "base64"
digest = OpenSSL::Digest.new('sha256')
key = [ 'xiIm9FuYhetyijXA2QL58TRlvhuSJ73FtdxiSNU2uHE=' ]
#this is just a dummy signature to show what the possible values are
signature = "200123123891:12|11231231|GET|just%20test%20value"
hmac = OpenSSL::HMAC.digest(digest, key.pack("m*"), signature)
str_signature = Base64.urlsafe_encode64(hmac)
example result: "B0NgX1hhW-rsnadD2_FF-grcw9pWghwMWgG47mU4J94="
Update:
Changed the pack method to output base64 strings.
Edited variable names for concistency
References:
Used hexdigest, has a different ouput string length.
This example uses the digest method, although I'm not sure what value the key parameter has, hopefully it's a base 64 encoded string.
This uses hexdigest again. I am pretty sure that digest is the method to go vs hexdigest, since hexdigest ouput has a longer string compared to the sample HMAC value I have from C# script.
Finally got the monkey of my back!
I don't really need to create a sha256 digest object after all, I just had to put the 'sha256' parameter.
require 'openssl'
require "base64"
#API_KEY = base64 encoded string
key = Base64.decode64(API_KEY)
hash = OpenSSL::HMAC.digest('sha256', key, "Message")
puts Base64.encode64(hash)
thanks to this link

Digital Signature and the identical passwords

If Two users enter the same password, The values in the DB will be the same.
Any advice , please
#region "Digital Signature"
public static String EncryptDS (this String dataToEncrypt)
{
//Convert dataToEncrypt to byte array
byte[] plainTextBytes = Encoding.Unicode.GetBytes(dataToEncrypt);
//===================================
//Create Crypto Service provides params (24 allows SHA256 hashing alogrithm)
CspParameters cspParams = new CspParameters();
cspParams = new CspParameters(24);
cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
cspParams.KeyContainerName = "TT_KEY";
//====================================
//Generate asymmetric key
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(cspParams);
//=====================================
//hash and then encrypt the hash(digitally sign)
byte[] sig = rsa.SignData(plainTextBytes, "SHA256");
//=====================================
//Return signed encrypt the hash (digitally sign)
return Convert.ToBase64String(sig);
}
Best practise for password hashing includes creating a reasonably long salt (random sequence of characters) which is stored in the database beside the password.
When hashing the password, concatenate it with the salt first. If two users have the same password, they will have different salts so the hashes will be different. This also helps prevent rainbow tables/mass brute forcing.

C# MD5 hashing needs to match PHP MD5 hashing (with salt)

I have a string that needs to be hashed using a salt in C# and match what it would in PHP. The C# code is as follows:
string stringToHash = "123";
string saltToUse = "321";
byte[] stringBytes = ASCIIEncoding.ASCII.GetBytes(stringToHash);
byte[] saltBytes = ASCIIEncoding.ASCII.GetBytes(saltToUse);
var hmacMD5 = new HMACMD5(saltBytes);
var saltedHash = hmacMD5.ComputeHash(stringBytes);
byte[] hashedBytesNoSalt = MD5CryptoServiceProvider.Create().ComputeHash(stringBytes);
string hashedString = BitConverter.ToString(hashedBytesNoSalt).Replace("-", "").ToLower();
string saltedString = BitConverter.ToString(saltedHash).Replace("-", "").ToLower();
The PHP code to test if the C# is outputting correctly is:
<?php echo md5('123'.'321'); ?>
The C# outputs the correct UNSALTED MD5 hash, which is 202cb962ac59075b964b07152d234b70. However, when I try to salt using C# I get 900011ae860f471561023fba6cc25df6 and with PHP I get c8837b23ff8aaa8a2dde915473ce0991.
I am not sure why it is doing this or if this is even the correct way. The thing to remember is the C# needs to output to what the PHP outputs to.
The C# code is using a better salting mechanism than the PHP code. To match the PHP, all you need to do is run MD5 on stringToHash + saltToUse instead of using the HMACMD5's key feature. In short, do exactly what you did to produce your unsalted MD5 in C#, but pass in stringToHash + saltToUse instead.
A salt is not the same thing as a private key. HMACMD5 uses a private key, when all that you desire is a salted MD5 hash. Just append the salt to the string to generate the correct key... stringToHash + saltToUse.
Read about HMACMD5 here: http://msdn.microsoft.com/en-us/library/yd9e7dt2.aspx

Categories