exporting a public key in pem format from x509certificate2 object - public-key-encryption

I'm new to this subject, and I got confused of the differences between a public key in PEM format vs CER format.
I'm trying to export a public key from a x509certificate2 object in PEM format in c# code.
As far as I understand, the difference between a certificate in cer format vs pem format, is only the header and footer
(if I understand correctly, a certificate in .cer format in base 64 should be someBase64String and in pem format it's the same string including the begin and end header and footer).
but my question is for the public key.
let pubKey be a public key exported in .cer format from an x509certificate2 object,
is the pem format of this key, will be:
------BEGIN PUBLIC KEY-----
pubKey...
------END PUBLIC KEY------
encoded in base 64?
Thanks :)

for the public key. let pubKey be a public key exported in .cer format from an x509certificate2 object
Talking about a ".cer format" only applies when you have the whole certificate; and that's all that an X509Certificate2 will export as. (Well, or a collection of certificates, or a collection of certificates with associated private keys).
Edit (2021-08-20):
Starting in .NET 6 you can use cert.PublicKey.ExportSubjectPublicKeyInfo() to get the DER-encoded SubjectPublicKeyInfo.
In .NET Core 3+/.NET 5+ you can use cert.GetRSAPublicKey()?.ExportSubjectPublicKeyInfo() (or whatever algorithm your key is)
In .NET 5+ you can turn those answers into PEM with PemEncoding.Write("PUBLIC KEY", spki)
Regardless of your .NET/.NET Core/.NET Framework version you can use the System.Formats.Asn1 package with AsnWriter to avoid the BuildSimpleDerSequence work (published 2020-11-09).
-- Original answer continues --
Nothing built in to .NET will give you the DER-encoded SubjectPublicKeyInfo block of the certificate, which is what becomes "PUBLIC KEY" under a PEM encoding.
You can build the data yourself, if you want. For RSA it's not too bad, though not entirely pleasant. The data format is defined in https://www.rfc-editor.org/rfc/rfc3280#section-4.1:
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING }
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameters ANY DEFINED BY algorithm OPTIONAL }
https://www.rfc-editor.org/rfc/rfc3279#section-2.3.1 describes how RSA keys, in particular are to be encoded:
The rsaEncryption OID is intended to be used in the algorithm field
of a value of type AlgorithmIdentifier. The parameters field MUST
have ASN.1 type NULL for this algorithm identifier.
The RSA public key MUST be encoded using the ASN.1 type RSAPublicKey:
RSAPublicKey ::= SEQUENCE {
modulus INTEGER, -- n
publicExponent INTEGER } -- e
The language behind these structures is ASN.1, defined by ITU X.680, and the way they get encoded to bytes is covered by the Distinguished Encoding Rules (DER) ruleset of ITU X.690.
.NET actually gives you back a lot of these pieces, but you have to assemble them:
private static string BuildPublicKeyPem(X509Certificate2 cert)
{
byte[] algOid;
switch (cert.GetKeyAlgorithm())
{
case "1.2.840.113549.1.1.1":
algOid = new byte[] { 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 };
break;
default:
throw new ArgumentOutOfRangeException(nameof(cert), $"Need an OID lookup for {cert.GetKeyAlgorithm()}");
}
byte[] algParams = cert.GetKeyAlgorithmParameters();
byte[] publicKey = WrapAsBitString(cert.GetPublicKey());
byte[] algId = BuildSimpleDerSequence(algOid, algParams);
byte[] spki = BuildSimpleDerSequence(algId, publicKey);
return PemEncode(spki, "PUBLIC KEY");
}
private static string PemEncode(byte[] berData, string pemLabel)
{
StringBuilder builder = new StringBuilder();
builder.Append("-----BEGIN ");
builder.Append(pemLabel);
builder.AppendLine("-----");
builder.AppendLine(Convert.ToBase64String(berData, Base64FormattingOptions.InsertLineBreaks));
builder.Append("-----END ");
builder.Append(pemLabel);
builder.AppendLine("-----");
return builder.ToString();
}
private static byte[] BuildSimpleDerSequence(params byte[][] values)
{
int totalLength = values.Sum(v => v.Length);
byte[] len = EncodeDerLength(totalLength);
int offset = 1;
byte[] seq = new byte[totalLength + len.Length + 1];
seq[0] = 0x30;
Buffer.BlockCopy(len, 0, seq, offset, len.Length);
offset += len.Length;
foreach (byte[] value in values)
{
Buffer.BlockCopy(value, 0, seq, offset, value.Length);
offset += value.Length;
}
return seq;
}
private static byte[] WrapAsBitString(byte[] value)
{
byte[] len = EncodeDerLength(value.Length + 1);
byte[] bitString = new byte[value.Length + len.Length + 2];
bitString[0] = 0x03;
Buffer.BlockCopy(len, 0, bitString, 1, len.Length);
bitString[len.Length + 1] = 0x00;
Buffer.BlockCopy(value, 0, bitString, len.Length + 2, value.Length);
return bitString;
}
private static byte[] EncodeDerLength(int length)
{
if (length <= 0x7F)
{
return new byte[] { (byte)length };
}
if (length <= 0xFF)
{
return new byte[] { 0x81, (byte)length };
}
if (length <= 0xFFFF)
{
return new byte[]
{
0x82,
(byte)(length >> 8),
(byte)length,
};
}
if (length <= 0xFFFFFF)
{
return new byte[]
{
0x83,
(byte)(length >> 16),
(byte)(length >> 8),
(byte)length,
};
}
return new byte[]
{
0x84,
(byte)(length >> 24),
(byte)(length >> 16),
(byte)(length >> 8),
(byte)length,
};
}
DSA and ECDSA keys have more complex values for AlgorithmIdentifier.parameters, but X509Certificate's GetKeyAlgorithmParameters() happens to give them back correctly formatted, so you would just need to write down their OID (string) lookup key and their OID (byte[]) encoded value in the switch statement.
My SEQUENCE and BIT STRING builders can definitely be more efficient (oh, look at all those poor arrays), but this would suffice for something that isn't perf-critical.
To check your results, you can paste the output to openssl rsa -pubin -text -noout, and if it prints anything other than an error you've made a legally encoded "PUBLIC KEY" encoding for an RSA key.

Like bartonjs said: "SubjectPublicKeyInfo becomes "PUBLIC KEY" under a PEM encoding, and nothing built in to .NET will give you the DER-encoded SubjectPublicKeyInfo block of the certificate."
Luckily here comes Bouncy Castle to rescue, with just few lines you can extract SubjectPublicKeyInfo (i.e Public key for PEM certificate) from X509Certificate2
Org.BouncyCastle.X509.X509Certificate bcert = new Org.BouncyCastle.X509.X509Certificate(x509Certificate2.RawData);
var publicKeyInfoBytes = bcert.CertificateStructure.SubjectPublicKeyInfo.GetDerEncoded();

Since .NET Core 3.0 (and .NET Standard 2.1) you can use ExportSubjectPublicKeyInfo method like this:
certificate.PublicKey.Key.ExportSubjectPublicKeyInfo()
In case PublicKey.Key throws an exception (supports only RSA and DSA), use one of ECDsaCertificateExtensions.GetECDsaPublicKey, RSACertificateExtensions.GetRSAPublicKey, DSACertificateExtensions.GetDSAPublicKey.

Related

VS2017 15.5.1 SSDT Security Change from TripleDES to AES256-CBC?

I am working to establish a CI pipeline for SSIS, with the first step automating the creation of ISPAC deployment packages. I am using the code from: https://github.com/rtumaykin/ssis-build which creates ssisbuild.exe. The executable reads the XML from dtproj, unencrypting secured information using CrptoAPIs for TripleDES as documented here
These two lines of code
var passwordDeriveBytes = new PasswordDeriveBytes(password, rgbSalt);
cryptoServiceProvider.Key = passwordDeriveBytes.CryptDeriveKey("TripleDES", "SHA1", 192, cryptoServiceProvider.IV);
Supply the password and salt from the dtproj file (converted to byte array with Convert.FromBase64String(saltXMLAttributeNode.Value)
The TripleDES conversion generates the exceptions:
"Specified initialization {IV} does not match the block size for this algorithm."
Looking into the VS2017 15.5 dtproj XML I find this section:
SSIS:Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc">
Indicating the DTPROJ Encryption Algorithm is now aes256-cbc.
And in fact, changing the code to use:
cryptoServiceProvider.Key = passwordDeriveBytes.CryptDeriveKey("AES", "SHA1", 256, cryptoServiceProvider.IV);
Removes the error.
But the next lines:
var passwordDeriveBytes = new PasswordDeriveBytes(password, rgbSalt);
cryptoServiceProvider.Key = passwordDeriveBytes.CryptDeriveKey("AES", "SHA1", 256, cryptoServiceProvider.IV)
Generates the new non-descript error:
"Object identifier (OID) is unknown"
Rewriting the decryption process using the examples in the AESCrptoServiceProvder Class from System.Security.Cryptography.Aes
using:
static string DecryptStringFromBytesAes(byte[] ciphertext, byte[] key, byte[] aesiv)
{
if (ciphertext == null || ciphertext.Length <= 0)
throw new ArgumentNullException("cypherText");
if (key == null || key.Length <=0)
throw new ArgumentNullException("Key");
if (aesiv == null || aesiv.Length <=0)
throw new ArgumentNullException("AESIV");
string plaintext = null;
using (AesCryptoServiceProvider aesAlg = new AesCryptoServiceProvider())
{
aesAlg.Key = key;
aesAlg.IV = aesiv;
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
using (MemoryStream msDecrypt = new MemoryStream(ciphertext))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrtypt = new StreamReader(csDecrypt))
{
plaintext = srDecrtypt.ReadToEnd();
}
}
}
}
return plaintext;
}
where Key = the Salt value from dtproj generates an error
"The specified key is not a valid size for this algorithm."
when loading the key at line:
using (AesCryptoServiceProvider aesAlg = new AesCryptoServiceProvider())
{
aesAlg.Key = key;
I am either supplying the incorrect value for the key or using the incorrect CryptoServiceProvider.
Does anyone know how encryption has been changed in VS2017 SSDT 15.5.1?

Bad Data cryptographicexception in xml using Linq in c#

My XML file:
<?xml version="1.0" encoding="utf-8"?>
<dskh>
<khachhang maso="kh01">
<ten_kh>thehung</ten_kh>
<tuoi_kh>15</tuoi_kh>
<dchi_kh>hochiminh</dchi_kh>
</khachhang>
<khachhang maso="kh02">
<ten_kh>hung</ten_kh>
<tuoi_kh>15</tuoi_kh>
<dchi_kh>hcm</dchi_kh>
</khachhang>
</dskh>
My Encrypt and Decrypt code:
class Program
{
// Call this function to remove the key from memory after use for security.
[System.Runtime.InteropServices.DllImport("KERNEL32.DLL", EntryPoint = "RtlZeroMemory")]
public static extern bool ZeroMemory(ref string Destination, int Length);
// Function to Generate a 64 bits Key.
static string GenerateKey()
{
// Create an instance of Symetric Algorithm. Key and IV is generated automatically.
DESCryptoServiceProvider desCrypto = (DESCryptoServiceProvider)DESCryptoServiceProvider.Create();
// Use the Automatically generated key for Encryption.
return ASCIIEncoding.ASCII.GetString(desCrypto.Key);
}
static void EncryptFile(string sInputFilename,
string sOutputFilename,
string sKey) {
FileStream fsInput = new FileStream(sInputFilename,FileMode.Open,FileAccess.Read);
FileStream fsEncrypted = new FileStream(sOutputFilename,FileMode.Create,FileAccess.Write);
DESCryptoServiceProvider DES = new DESCryptoServiceProvider();
DES.Key = Encoding.UTF8.GetBytes(sKey);
DES.IV = Encoding.UTF8.GetBytes(sKey);
ICryptoTransform desencrypt = DES.CreateEncryptor();
CryptoStream cryptostream = new CryptoStream(fsEncrypted,desencrypt,CryptoStreamMode.Write);
byte[] bytearrayinput = new byte[fsInput.Length - 1];
fsInput.Read(bytearrayinput, 0, bytearrayinput.Length);
cryptostream.Write(bytearrayinput, 0, bytearrayinput.Length);
}
static void DecryptFile(string sInputFilename,
string sOutputFilename,
string sKey)
{
DESCryptoServiceProvider DES = new DESCryptoServiceProvider();
//A 64 bit key and IV is required for this provider.
//Set secret key For DES algorithm.
DES.Key = Encoding.UTF8.GetBytes(sKey);
//Set initialization vector.
DES.IV = Encoding.UTF8.GetBytes(sKey);
//Create a file stream to read the encrypted file back.
FileStream fsread = new FileStream(sInputFilename,
FileMode.Open,
FileAccess.Read);
//Create a DES decryptor from the DES instance.
ICryptoTransform desdecrypt = DES.CreateDecryptor();
//Create crypto stream set to read and do a
//DES decryption transform on incoming bytes.
CryptoStream cryptostreamDecr = new CryptoStream(fsread,
desdecrypt,
CryptoStreamMode.Read);
//Print the contents of the decrypted file.
StreamWriter fsDecrypted = new StreamWriter(sOutputFilename);
fsDecrypted.Write(new StreamReader(cryptostreamDecr).ReadToEnd());
fsDecrypted.Flush();
fsDecrypted.Close();
}
static void Main(string[] args)
{
// Must be 64 bits, 8 bytes.
// Distribute this key to the user who will decrypt this file.
string sSecretKey;
// Get the key for the file to encrypt.
sSecretKey = GenerateKey();
// For additional security pin the key.
GCHandle gch = GCHandle.Alloc(sSecretKey, GCHandleType.Pinned);
// Encrypt the file.
EncryptFile(#"C:\xml_kh.xml",
#"C:\xml_encry.xml",
sSecretKey);
//Decrypt the file.
DecryptFile(#"C:\xml_encry.xml",
#"C:\xml_decry.xml",
sSecretKey);
}
}
When i excute Decrypt code, i get Cryptographicexception: Bad Data here:
//Print the contents of the decrypted file.
StreamWriter fsDecrypted = new StreamWriter(sOutputFilename);
fsDecrypted.Write(new StreamReader(cryptostreamDecr).ReadToEnd());
fsDecrypted.Flush();
fsDecrypted.Close();
Please help me!!!
Add two lines at the end of EncryptFile function:
cryptostream.Flush();
cryptostream.Close();
EDIT
I missed there is one more error:
byte[] bytearrayinput = new byte[fsInput.Length - 1];
s/b
byte[] bytearrayinput = new byte[fsInput.Length];

Find public key from certificate as a xml string

i need to find the public key from certificate as xml string. I can take the public key only as a string with this method:
public string CelesiPublik()
{
X509Certificate cer;
cer = X509Certificate.CreateFromCertFile("C://certificate//EdonBajrami.crt");
string Celesi = cer.GetPublicKeyString();
return Celesi;
}
and then i take this value to the another method. Celesi value now has take celesiPublik
celesiPublik = e.Result;
string NrTelefonit = "044-419-109";
string salt = "VotimiElektronikKosove";
AesManaged aes = new AesManaged();
Rfc2898DeriveBytes rfc2898 = new Rfc2898DeriveBytes(celesiPublik,Encoding.UTF8.GetBytes(salt));
aes.Key = rfc2898.GetBytes(aes.KeySize / 8);
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.FromXmlString(celesiPublik);
rsa.Encrypt(aes.Key, false);
but it shows me error. How can i resolve this problem?
GregS i cannot use in Windows Phone 7 X509Certificate2. I take my public key with the method:
`X509Certificate cer = new X509Certificate("C://certificate//EdonBajrami.crt");
string publicKey = cer.GetPublicKeyString();`
i can take the public key. Then in another method i take value of the publicKey to another string variable Key:
`string Key = publicKey;
//-----First------
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
//----------
RSAParameters RSAKeyInfo = new RSAParameters();
byte[] celesibyte = System.Text.Encoding.UTF8.GetBytes(celesiPublik);
byte[] Exponent = { 1, 0, 1 };
RSAKeyInfo.Modulus = celesibyte;
RSAKeyInfo.Exponent = Exponent;
rsa.ImportParameters(RSAKeyInfo);
//-----------
/* rsa.FromXmlString(celesi2Publik); */
string edon = "Edon";
byte[] edon1 = Encoding.UTF8.GetBytes(edon);
byte[] edon2 = rsa.Encrypt(edon1, false);'
but it sends me data that is 506 byte, i dont understand why, what is a problem ?
As far as I can tell the X509Certificate class is nearly useless. Perhaps that is why there is a class X509Certificate2? Use the X509Certificate2 class. You can create an RSACryptoServiceProvider directly from the public key like the following:
X509Certificate2 cert = new X509Certificate2(X509Certificate.CreateFromCertFile("C://certificate//EdonBajrami.crt"));
RSACryptoServiceProvider rsa = (RSACryptoServiceProvider) cert.PublicKey.Key;

Windows Phone - Poor encryption performance

I have used following code for encryption in Windows Phone:
public static string Encrypt(string dataToEncrypt, string password)
{
AesManaged aes = null;
MemoryStream memoryStream = null;
CryptoStream cryptoStream = null;
string salt = "12345678";
try
{
// Generate a Key based on a Password and HMACSHA1 pseudo-random number generator
// Salt must be at least 8 bytes long
// Use an iteration count of at least 1000
Rfc2898DeriveBytes rfc2898 = new Rfc2898DeriveBytes(password, Encoding.UTF8.GetBytes(salt), 10000);
// Create AES algorithm
aes = new AesManaged();
// Key derived from byte array with 32 pseudo-random key bytes
aes.Key = rfc2898.GetBytes(32);
// IV derived from byte array with 16 pseudo-random key bytes
aes.IV = rfc2898.GetBytes(16);
// Create Memory and Crypto Streams
memoryStream = new MemoryStream();
cryptoStream = new CryptoStream(memoryStream, aes.CreateEncryptor(), CryptoStreamMode.Write);
// Encrypt Data
byte[] data = Encoding.UTF8.GetBytes(dataToEncrypt);
cryptoStream.Write(data, 0, data.Length);
cryptoStream.FlushFinalBlock();
// Return Base 64 String
return Convert.ToBase64String(memoryStream.ToArray());
}
finally
{
if (cryptoStream != null)
{
cryptoStream.Close();
}
if (memoryStream != null)
{
memoryStream.Close();
}
if (aes != null)
{
aes.Clear();
}
}
}
public static string Decrypt(string dataToDecrypt, string password)
{
AesManaged aes = null;
MemoryStream memoryStream = null;
string salt = "12345678";
try
{
// Generate a Key based on a Password and HMACSHA1 pseudo-random number generator
// Salt must be at least 8 bytes long
// Use an iteration count of at least 1000
Rfc2898DeriveBytes rfc2898 = new Rfc2898DeriveBytes(password, Encoding.UTF8.GetBytes(salt), 10000);
// Create AES algorithm
aes = new AesManaged();
// Key derived from byte array with 32 pseudo-random key bytes
aes.Key = rfc2898.GetBytes(32);
// IV derived from byte array with 16 pseudo-random key bytes
aes.IV = rfc2898.GetBytes(16);
// Create Memory and Crypto Streams
memoryStream = new MemoryStream();
CryptoStream cryptoStream = new CryptoStream(memoryStream, aes.CreateDecryptor(), CryptoStreamMode.Write);
// Decrypt Data
byte[] data = Convert.FromBase64String(dataToDecrypt);
cryptoStream.Write(data, 0, data.Length);
cryptoStream.FlushFinalBlock();
// Return Decrypted String
byte[] decryptBytes = memoryStream.ToArray();
// Dispose
if (cryptoStream != null)
{
cryptoStream.Dispose();
}
// Retval
return Encoding.UTF8.GetString(decryptBytes, 0, decryptBytes.Length);
}
finally
{
if (memoryStream != null)
{
memoryStream.Dispose();
}
if (aes != null)
{
aes.Clear();
}
}
}
The performance of encyrption is very poor. Can anyone suggest some improvement on above code?
Sure, you could move the key derivation code using Rfc2898DeriveBytes outside of those function since the key for a given password will be constant and will be usually used multiple times. Other than that I don't see much room for improvement.
Perhaps you should introduce some using() brackets to make sure there are no memory leaks. You could have a look at this:
http://zayko.net/post/How-to-EncryptDecrypt-a-String-in-Silverlight-for-Windows-Phone-7.aspx
This would only help if your function became slower over time, instead on the first run.

SHA1 with salt on windows phone 7

I have some some time now reshearchd how to encode a password to SHA1 with a salt.
The is the code i used on my web application part, but it will not work on a phone environment.
public class Password
{
private string _password;
private int _salt;
public Password(string strPassword, int nSalt)
{
_password = strPassword;
_salt = nSalt;
}
public string ComputeSaltedHash()
{
// Create Byte array of password string
ASCIIEncoding encoder = new ASCIIEncoding();
Byte[] _secretBytes = encoder.GetBytes(_password);
// Create a new salt
Byte[] _saltBytes = new Byte[4];
_saltBytes[0] = (byte)(_salt >> 24);
_saltBytes[1] = (byte)(_salt >> 16);
_saltBytes[2] = (byte)(_salt >> 8);
_saltBytes[3] = (byte)(_salt);
// append the two arrays
Byte[] toHash = new Byte[_secretBytes.Length + _saltBytes.Length];
Array.Copy(_secretBytes, 0, toHash, 0, _secretBytes.Length);
Array.Copy(_saltBytes, 0, toHash, _secretBytes.Length, _saltBytes.Length);
SHA1 sha1 = SHA1.Create();
Byte[] computedHash = sha1.ComputeHash(toHash);
return encoder.GetString(computedHash);
}
public static int CreateRandomSalt()
{
Byte[] _saltBytes = new Byte[4];
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
rng.GetBytes(_saltBytes);
return ((((int)_saltBytes[0]) << 24) + (((int)_saltBytes[1]) << 16) +
(((int)_saltBytes[2]) << 8) + ((int)_saltBytes[3]));
}
public static string CreateRandomPassword(int PasswordLength)
{
String _allowedChars = "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ23456789!\"#ยค%&/()=?$+-_.,;'*";
Byte[] randomBytes = new Byte[PasswordLength];
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
rng.GetBytes(randomBytes);
char[] chars = new char[PasswordLength];
int allowedCharCount = _allowedChars.Length;
for (int i = 0; i < PasswordLength; i++)
{
chars[i] = _allowedChars[(int)randomBytes[i] % allowedCharCount];
}
return new string(chars);
}
}
Silverlight and Windows Phone 7 do not have an ASCIIEncoding. I suggest you use the UTF8Encoding instead. If you are certain that your passwords are always within the ASCII range then this encoding will work the same as the ASCIIEncoding would of had it been present.
If on the other hand you cannot guarantee that passwords are always within the ASCII range then you would need to make sure both ends hash using the UTF8Encoding to ensure generated hashs are the same.

Resources