Weak Key error on Github, with 4096 RSAkey generated with C# - c#

I am generating a RSA KEY with 4096 bits according to RFC4716 (Or at least I thought so) using C# and the standard cryptography library, however git hub says I have a key with the wrong size, returning the following error when I try to add it to the keys associated to my account.
This is the code to generate the key:
public static void GenerateKeys()
{
// Create the CspParameters object and set the key container
// name used to store the RSA key pair.
CspParameters cp = new CspParameters();
//cp.KeyContainerName = ContainerName;
CspKeyContainerInfo info = new CspKeyContainerInfo(cp);
//string filename = info.UniqueKeyContainerName;
// Create a new instance of RSACryptoServiceProvider that accesses
// the key container MyKeyContainerName.
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(4096, cp);
var test = rsa.KeySize;
using (StreamWriter privateKeyWriter = new StreamWriter(GitStandard.PrivateSSHKeyPath))
{
ExportPrivateKey(rsa, privateKeyWriter);
}
using (StreamWriter publicKeyWriter = new StreamWriter(GitStandard.PublicSSHKeyPath))
{
ExportPublicKeyOpenSSH(rsa, publicKeyWriter);
}
}
The method ExportPublicKeyOpenSSH is a small modification of the code found in this thread with answers on how to convert the key to RFC4716, the only thing I do differently is to add a zero (0) before the modulus is converted.
private static void ExportPublicKeyOpenSSH(RSACryptoServiceProvider csp, TextWriter outputStream)
{
var parameters = csp.ExportParameters(false);
byte[] sshrsa_bytes = Encoding.Default.GetBytes("ssh-rsa");
//initializing modulus array
byte[] n = new Byte[parameters.Modulus.Length + 1];
//adding initial zero before modulus to conform with OpenSSH
n[0] = 0;
System.Buffer.BlockCopy(parameters.Modulus, 0, n, 1, parameters.Modulus.Length);
//byte[] n = parameters.Modulus;
byte[] e = parameters.Exponent;
System.Array.Resize<Byte>(ref n, n.Length + 1);
string base64;
using (var stream = new MemoryStream())
{
stream.Write(ToBytes(sshrsa_bytes.Length), 0, 4);
stream.Write(sshrsa_bytes, 0, sshrsa_bytes.Length);
stream.Write(ToBytes(e.Length), 0, 4);
stream.Write(e, 0, e.Length);
stream.Write(ToBytes(n.Length), 0, 4);
stream.Write(n, 0, n.Length);
stream.Flush();
base64 = Convert.ToBase64String(stream.ToArray());
}
var result = string.Format("ssh-rsa {0}", base64);
outputStream.Write(result);
}
What the key generated looks like
ssh-rsa
AAAAB3NzaC1yc2EAAAADAQABAAACAgD171Y9VeinRALRfU8adS2K0vYHGfKkQwqs8SOQbhURFNtazupsocmpW96dYF346UVVCiKQCYrCW6t0QpGE3ch7onqTvXBszA9mfcuLX9hlhesJqFyUTHxDUopCc2tc5fWYuZ4MeySKuOetBEmPfN3Eu+SWC8j3VS9YzIDjwhkBPcJoxOnv3l7pSxEzGBGQXwdGmL8TFxxsBhue1ajralYPXgJo1nra70ChHcr8PfJvIXigBYCkwnb0KuofbPyhHETo4fNJqCPa1rLjnKoz5iTpyak2SWnhD5FX0/t4juaL/OKNE4YSaAqpWwA9VS1i+y7doeSRc22tm5LHgSLmlxg6h5lPKm5emB840eMLOPvZLS/4uODzFPMo4NFC2ZwNwdlXhcQE9EVtz9EZox1isKpJgShqJPh0sHVH9RnCuBSxW5N79KtsvcXI2zAiLBczKukqU2rTkvYdV1Wkx4zHSvLe42PQuJvSwhwW1tlgyFemd2aRwGDltQyGTPNOZ28E6SGgvxYtB4nvcu8gLyxob4Hz3ysohDB0Z9ZEismSK/8eSeMrBPosTBO77tsjUk1L8v2lHXQ+p1raLpd3ETeae7vZjt6zMFCIhNKDvdJL9b0mIKLB26PMhWG4DzSTJGeIANjiNryWK7y0gdgdPs5953H1EJVRQ0wd2ceFFg2+kpqlrQA=
Using the command ssh-keygen -l -f custom_rsa.pub to test the validity of the key.
$ ssh-keygen -l -f custom_rsa.pub
4104 SHA256:uGO4sHOXXuX1waf+8jrdsWr3/57npF5AuUKUgYVWbCI no comment (RSA)

You resize n both to add a 0 on the left (by manually copying into it starting at index 1) and to add a 0 on the right (via Array.Resize). The latter one is probably getting you into trouble.
Also, (unrelated) you probably shouldn't use Encoding.Default, but rather whatever encoding you intend. Encoding.ASCII, probably.

Related

Cross-Platform C# Crypto: Cannot generate right key/Choose right algorithm

I am creating a crypto class to use in both my chat client(Windows, .NET Framework) & server(Linux, .NET Core).
I figured I'd use BouncyCastle since its "Well documented", and since i need cross-platform which default lib doesn't support (CNG classes).
So i got the key generation working (Haven't tested it cross-platform yet), but the encryption and decryption is not working dues to invalid key size.
I cant find any docs on this and have been stuck here for a while.
Please point out if there's anything I'm doing horribly wrong
(I'm very new to crypto, C#... Well programming in general, been learning for 1 year)
I really hope my code isn't a total mess:
using Org.BouncyCastle.Asn1.Nist;
using Org.BouncyCastle.Asn1.Sec;
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Math.EC;
using Org.BouncyCastle.Security;
using System;
using System.IO;
using System.Text;
namespace ChatClient
{
public class Crypto
{
private bool _ready = false;
private X9ECParameters m_x9EC;
private ECPublicKeyParameters m_myPubKey;
private AsymmetricKeyParameter m_myPrivKey;
private byte[] m_sharedSecret = null;
public Crypto()
{
// Get curve
m_x9EC = NistNamedCurves.GetByName("P-521");
ECDomainParameters ecDomain = new ECDomainParameters(m_x9EC.Curve, m_x9EC.G, m_x9EC.N, m_x9EC.H, m_x9EC.GetSeed());
// Create generator
ECKeyPairGenerator g = (ECKeyPairGenerator)GeneratorUtilities.GetKeyPairGenerator("ECDH");
g.Init(new ECKeyGenerationParameters(ecDomain, new SecureRandom()));
// Generate keypair
AsymmetricCipherKeyPair keyPair = g.GenerateKeyPair();
// Set keys
m_myPubKey = (ECPublicKeyParameters)keyPair.Public;
m_myPrivKey = keyPair.Private;
}
public void GenPrivateKey(byte[] key)
{
// Why whould anyone even...
if (key == null)
throw new ArgumentNullException();
// Split up the Base64-Encoded, comma-seperated crypto-coords of the server/client
string str = Encoding.UTF8.GetString(key);
string[] elements = str.Split(',');
if (elements.Length != 2)
throw new ArgumentException();
// Generate a key out of the coordinates
ECPoint point = m_x9EC.Curve.CreatePoint(
new BigInteger(Convert.FromBase64String(elements[0])),
new BigInteger(Convert.FromBase64String(elements[1]))
);
// Get public key
ECPublicKeyParameters remotePubKey = new ECPublicKeyParameters("ECDH", point, SecObjectIdentifiers.SecP521r1);
// Generate shared secret key
IBasicAgreement aKeyAgree = AgreementUtilities.GetBasicAgreement("ECDH");
aKeyAgree.Init(m_myPrivKey);
m_sharedSecret = aKeyAgree.CalculateAgreement(remotePubKey).ToByteArray();
// Debugging...
Console.WriteLine("Key: {0}", Convert.ToBase64String(m_sharedSecret));
// Authentication is done, class can now be used for encryption/decryption
_ready = true;
}
public byte[] GetPublicKey()
{
// Assemble the Base64-Encoded, comma-seperated crypto-coords to send to the other client/server
string str = string.Format(
"{0},{1}",
Convert.ToBase64String(m_myPubKey.Q.AffineXCoord.ToBigInteger().ToByteArray()),
Convert.ToBase64String(m_myPubKey.Q.AffineYCoord.ToBigInteger().ToByteArray())
);
// Return it
return Encoding.UTF8.GetBytes(str);
}
public byte[] Encrypt(byte[] unencryptedData)
{
// Keys need to be generated before we can start encrypting/decrypring, And please dont pass null into here...
if (!_ready || unencryptedData == null)
return null;
using (MemoryStream ms = new MemoryStream())
{
using (System.Security.Cryptography.AesManaged cryptor = new System.Security.Cryptography.AesManaged())
{
// Set parameters
cryptor.Mode = System.Security.Cryptography.CipherMode.CBC;
cryptor.Padding = System.Security.Cryptography.PaddingMode.PKCS7;
cryptor.KeySize = 128;
cryptor.BlockSize = 128;
// Get iv
byte[] iv = cryptor.IV;
// Encrypt the data
using (System.Security.Cryptography.CryptoStream cs = new System.Security.Cryptography.CryptoStream(ms, cryptor.CreateEncryptor(m_sharedSecret, iv), System.Security.Cryptography.CryptoStreamMode.Write))
cs.Write(unencryptedData, 0, unencryptedData.Length);
// Get stuff that was encrpyted
byte[] encryptedContent = ms.ToArray();
// Create a new array for the data + iv
byte[] result = new byte[iv.Length + encryptedContent.Length];
//copy both arrays into one
System.Buffer.BlockCopy(iv, 0, result, 0, iv.Length);
System.Buffer.BlockCopy(encryptedContent, 0, result, iv.Length, encryptedContent.Length);
// Aaaand return it
return result;
}
}
return null;
}
public byte[] Decrypt(byte[] encryptedData)
{
// Keys need to be generated before we can start encrypting/decrypring, And please dont pass null into here...
if (!_ready || encryptedData == null)
return null;
// New arrays for iv and data
byte[] iv = new byte[16];
byte[] dat = new byte[encryptedData.Length - iv.Length];
// Get iv and data
System.Buffer.BlockCopy(encryptedData, 0, iv, 0, iv.Length);
System.Buffer.BlockCopy(encryptedData, iv.Length, dat, 0, dat.Length);
using (MemoryStream ms = new MemoryStream())
{
using (System.Security.Cryptography.AesManaged cryptor = new System.Security.Cryptography.AesManaged())
{
// Set parameters
cryptor.Mode = System.Security.Cryptography.CipherMode.CBC;
cryptor.Padding = System.Security.Cryptography.PaddingMode.PKCS7;
cryptor.KeySize = 128;
cryptor.BlockSize = 128;
// Decrypt the data
using (System.Security.Cryptography.CryptoStream cs = new System.Security.Cryptography.CryptoStream(ms, cryptor.CreateDecryptor(m_sharedSecret, iv), System.Security.Cryptography.CryptoStreamMode.Write))
cs.Write(encryptedData, 0, encryptedData.Length);
// Aaaand return it
return ms.ToArray();
}
}
return null;
}
}
}
Where this part:
// Encrypt the data
using (System.Security.Cryptography.CryptoStream cs = new System.Security.Cryptography.CryptoStream(ms, cryptor.CreateEncryptor(m_sharedSecret, iv), System.Security.Cryptography.CryptoStreamMode.Write))
Throws this error:
Exception thrown: 'System.ArgumentException' in System.Core.dll
An unhandled exception of type 'System.ArgumentException' occurred in System.Core.dll
The specified key is not a valid size for this algorithm.
Closing this question, it was answered by Topaco in the comments
In the code the shared secret is used directly as symmetric key, which causes the error due to the different sizes. Instead, the symmetric key (with the appropriate size) is usually derived from the shared secret, here and here.

ECDiffieHellmanPublicKey from ByteArray (using ECDiffieHellman NamedCurves)

I'm working on communication nodejs -> c# server.
I need to secure connection between them so I chode ECDiffieHellman as the key exchange mechanism (nodejs supports it). I had some problem with it... Just my lack of knowledge so I've made my lesson and now I can generate and export keys as base64 and nodejs have no problem with accepting c# key but on the other side c# ... won't even take his own key ...
error System.Security.Cryptography.CryptographicException: 'The parameter is incorrect.'
Ye I know I'm making sth wrong but what?
using (ECDiffieHellman alice = ECDiffieHellman.Create(ECCurve.NamedCurves.brainpoolP256r1))
{
var alicePublicKey = Convert.ToBase64String(alice.PublicKey.ToByteArray());
//NODEJS brainpoolP256r1 publickey
var key1 = Convert.FromBase64String("BB92GQLod55fXEhgNxwQcPQFFvph7eIjnSzdNz2PhzUAOcaPEiLBPQR6AL5pqVLFram8OtPapoBGYZn2vaGl+/U=").ToList();
//test
var key2 = Convert.FromBase64String(alicePublicKey);
var keyType = new byte[] { 0x45, 0x43, 0x4B, 0x50 };
var keyLength = new byte[] { 0x20, 0x00, 0x00, 0x00 };
key1.RemoveAt(0);
key1 = keyType.Concat(keyLength).Concat(key1).ToList();
byte[] bobKeyBytes = key1.ToArray();
ECDiffieHellmanPublicKey k = ECDiffieHellmanCngPublicKey.FromByteArray(bobKeyBytes, new CngKeyBlobFormat("ECCPUBLICBLOB")); //error System.Security.Cryptography.CryptographicException: 'The parameter is incorrect.'
ECDiffieHellmanPublicKey kk = ECDiffieHellmanCngPublicKey.FromByteArray(key2, new CngKeyBlobFormat("ECCPUBLICBLOB")); // error System.Security.Cryptography.CryptographicException: 'The parameter is incorrect.'
byte[] aliceKey = alice.DeriveKeyMaterial(k);
byte[] encryptedMessage = null;
byte[] iv = null;
// Send(aliceKey, "Secret message", out encryptedMessage, out iv);
}
you can find rest of the story there
ECDH nodejs and C# key exchange
You're asserting that the base64 contents that go into key1 are for brainpoolP256r1.
Decoding the value we see that it's a 65 byte payload starting with 04, which looks like an uncompressed point encoding for a curve with a 256-bit prime. So far so good.
You've even correctly used BCRYPT_ECDH_PUBLIC_GENERIC_MAGIC, but you can't import a "generic named key blob" without specifying the import property that tells it which curve.
The easy way that you load the key from this point is
byte[] keyX = new byte[key1.Length / 2];
byte[] keyY = new byte[keyX.Length];
Buffer.BlockCopy(key1, 1, keyX, 0, keyX.Length);
Buffer.BlockCopy(key1, 1 + keyX.Length, keyY, 0, keyY.Length);
ECParameters parameters = new ECParameters
{
Curve = ECCurve.NamedCurves.brainpoolP256r1,
Q =
{
X = keyX,
Y = keyY,
},
};
byte[] derivedKey;
using (ECDiffieHellman bob = ECDiffieHellman.Create(parameters))
using (ECDiffieHellmanPublicKey bobPublic = bob.PublicKey)
{
derivedKey = alice.DeriveKeyFromHash(bobPublic, HashAlgorithmName.SHA256);
}
I've gone ahead and expanded the DeriveKeyMaterial method into what it means by default with an ECDiffieHellmanCng, since other types of ECDH don't support that method (due to its low specificity of behavior).

Cryptopp Ephemeral Diffie Hellman to C# Bouncy Castle

I'm trying to convert cryptopp DiffieHellman 2 key agreement method to c# bouncy castle library.
Here is the help page about cryptoo c++ library: http://www.cryptopp.com/wiki/Diffie-Hellman
I'm trying to implement RFC 5114's 1024-bit MODP group to C#.
But there are a lot of problems I couldn't solve.
When keyPair generates a key, it is 131 bytes, but it must be 128 bytes, because server sending to me 256 bytes key with static and ephemeral key. I must send 256 bytes too .But 1-, 2-, and 3-byte values are static in every key so I'm removing first 3 bytes in keys is it true?
Which secret key must I use for converting shared secret to other encryption system keys?
Example I have a secret key, how can I convert it for Twofish, RC6, xTEA, Serpent etc?
Here is my code:
public byte[] CreateaNewDiffieHellmanKey()
{
public static string Phex = "B10B8F96A080E01DDE92DE5EAE5D54EC52C99FBCFB06A3C69A6A9DCA52D23B616073E28675A23D189838EF1E2EE652C013ECB4AEA906112324975C3CD49B83BFACCBDD7D90C4BD7098488E9C219A73724EFFD6FAE5644738FAA31A4FF55BCCC0A151AF5F0DC8B4BD45BF37DF365C1A65E68CFDA76D4DA708DF1FB2BC2E4A4371";
public static string Ghex = "A4D1CBD5C3FD34126765A442EFB99905F8104DD258AC507FD6406CFF14266D31266FEA1E5C41564B777E690F5504F213160217B4B01B886A5E91547F9E2749F4D7FBD7D3B9A92EE1909D0D2263F80A76A6A24C087A091F531DBF0A0169B6A28AD662A4D18E73AFA32D779D5918D08BC8858F4DCEF97C2A24855E6EEB22B3B2E5";
public static string Qhex = "F518AA8781A8DF278ABA4E7D64B7CB9D49462353";
BigInteger P = new BigInteger(Phex, 16);
BigInteger G = new BigInteger(Ghex, 16);
BigInteger Q = new BigInteger(Qhex, 16);
IAsymmetricCipherKeyPairGenerator staticKeyGen = GeneratorUtilities.GetKeyPairGenerator("DH");
IAsymmetricCipherKeyPairGenerator ephemeralKeyGen = GeneratorUtilities.GetKeyPairGenerator("DH");
DHParameters dhParams = new DHParameters(P, G, Q, 0, 160);
DHP = dhParams;
KeyGenerationParameters kgpSt = new DHKeyGenerationParameters(new SecureRandom(), dhParams);
KeyGenerationParameters kgpEp = new DHKeyGenerationParameters(new SecureRandom(), dhParams);
staticKeyGen.Init(kgpSt);
ephemeralKeyGen.Init(kgpEp);
AsymmetricCipherKeyPair staticKeyPayir = staticKeyGen.GenerateKeyPair();
staticKeyEgri = AgreementUtilities.GetBasicAgreement("DH");
staticKeyEgri.Init(staticKeyPayir.Private);
AsymmetricCipherKeyPair ephemeralKeyPair = ephemeralKeyGen.GenerateKeyPair();
ephemeralKeyEgri = AgreementUtilities.GetBasicAgreement("DH");
ephemeralKeyEgri.Init(staticKeyPayir.Private);
AsymmetricKeyParameter StaticPublicKey = staticKeyPayir.Public;
SubjectPublicKeyInfo StaticPublicKeyinfomuz = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(StaticPublicKey);
byte[] st1 = StaticPublicKeyinfomuz.PublicKeyData.GetBytes();
byte[] staticPublic = new byte[128];
Array.Copy(st1, 3, staticPublic, 0, staticPublic.Length);
AsymmetricKeyParameter EphPublicKey = staticKeyPayir.Public;
SubjectPublicKeyInfo EphPublicKeyinfomuz = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(EphPublicKey);
byte[] ep1 = StaticPublicKeyinfomuz.PublicKeyData.GetBytes();
byte[] ephemeralPublic = new byte[128];
Array.Copy(ep1, 3, ephemeralPublic, 0, ephemeralPublic.Length);
return Bot.Birleştir(staticPublic, ephemeralPublic); // Combine 2 byte arrays
}
public bool AgreeTheKey(byte[] buffer)
{
byte[] staticpublic = new byte[128];
byte[] ephemeralpublic = new byte[128];
Array.Copy(buffer, 0, staticpublic, 0, staticpublic.Length);
Array.Copy(buffer, staticpublic.Length, ephemeralpublic, 0, ephemeralpublic.Length);
ICipherParameters istatic = new KeyParameter(staticpublic);
ICipherParameters iphemeral = new KeyParameter(ephemeralpublic);
DHPublicKeyParameters dhkpST = new DHPublicKeyParameters(new BigInteger(staticpublic), DHP);
DHPublicKeyParameters dhkpEP = new DHPublicKeyParameters(new BigInteger(staticpublic), DHP);
SharedStatic = staticKeyEgri.CalculateAgreement(dhkpST).ToByteArray();
SharedEphemeral = ephemeralKeyEgri.CalculateAgreement(dhkpEP).ToByteArray();
byte[] Sharedkey = SharedStatic;
return true;
}
If you don't want to have the added encoding, you may always cast from AsymmetricKeyParameter to DHPublicKeyParameters and retrieve Y using getY(). Of course, after getting the value as an integer, you may still want to encode it to a fixed number of octets (bytes). In general, unsigned, left padded big endian encoding is used for the numbers.

How can I use ConvertTo-SecureString

Let's say I need to do this in Powershell:
$SecurePass = Get-Content $CredPath | ConvertTo-SecureString -Key (1..16)
[String]$CleartextPass = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($CredPass));
The content of $CredPath is a file that contains the output of ConvertFrom-SecureString -Key (1..16).
How do I accomplish the ConvertTo-SecureString -key (1..16) portion in C#/.NET?
I know how to create a SecureString, but I'm not sure how the encryption should be handled.
Do I encrypt each character using AES, or decrypt the string and then create a the secure string per character?
I know next to nothing about cryptography, but from what I've gathered I might just want to invoke the Powershell command using C#.
For reference, I found a similar post about AES encryption/decryption here:
Using AES encryption in C#
UPDATE
I have reviewed the link Keith posted, but I face additional unknowns. The DecryptStringFromBytes_Aes takes three arguments:
static string DecryptStringFromBytes_Aes(byte[] cipherText, byte[] Key, byte[] IV)
The first argument is a byte array represents the encrypted text. The question here is, how should the string be represented in the byte array? Should it be represented with or without encoding?
byte[] ciphertext = Encoding.ASCII.GetBytes(encrypted_text);
byte[] ciphertext = Encoding.UTF8.GetBytes(encrypted_text);
byte[] ciphertext = Encoding.Unicode.GetBytes(encrypted_text);
byte[] ciphertext = new byte[encrypted_password.Length * sizeof(char)];
System.Buffer.BlockCopy(encrypted_password.ToCharArray(), 0, text, 0, text.Length);
The second byte array is the key should simply be an array of integers:
byte[] key = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 };
The third byte array is an "Initialization Vector" - it looks like the Aes.Create() call will generate a byte[] for IV randomly. Reading around, I've found that I might need to use the same IV. As ConvertFrom-SecureString and ConvertTo-SecureString are able to encrypt/decrypt using simply the key, I am left with the assumption that the IV[] can be random -or- has a static definition.
I have not yet found a winning combination, but I will keep trying.
I know this is an old post. I am posting this for completeness and posterity, because I couldn't find a complete answer on MSDN or stackoverflow. It will be here in case I ever need to do this again.
It is a C# implementation of of powershell's ConvertTo-SecureString with AES encryption (turned on by using the -key option). I will leave it for exercise to code a C# implementation of ConvertFrom-SecureString.
# forward direction
[securestring] $someSecureString = read-host -assecurestring
[string] $psProtectedString = ConvertFrom-SecureString -key (1..16) -SecureString $someSecureString
# reverse direction
$back = ConvertTo-SecureString -string $psProtectedString -key (1..16)
My work is combining answers and re-arranging user2748365's answer to be more readable and adding educational comments! I also fixed the issue with taking a substring -- at the time of this post, his code only has two elements in strArray.
using System.IO;
using System.Text;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography;
using System.Globalization;
// psProtectedString - this is the output from
// powershell> $psProtectedString = ConvertFrom-SecureString -SecureString $aSecureString -key (1..16)
// key - make sure you add size checking
// notes: this will throw an cryptographic invalid padding exception if it cannot decrypt correctly (wrong key)
public static SecureString ConvertToSecureString(string psProtectedString, byte[] key)
{
// '|' is indeed the separater
byte[] asBytes = Convert.FromBase64String( psProtectedString );
string[] strArray = Encoding.Unicode.GetString(asBytes).Split(new[] { '|' });
if (strArray.Length != 3) throw new InvalidDataException("input had incorrect format");
// strArray[0] is a static/magic header or signature (different passwords produce
// the same header) It unused in our case, looks like 16 bytes as hex-string
// you know strArray[1] is a base64 string by the '=' at the end
// the IV is shorter than the body, and you can verify that it is the IV,
// because it is exactly 16bytes=128bits and it decrypts the password correctly
// you know strArray[2] is a hex-string because it is [0-9a-f]
byte[] magicHeader = HexStringToByteArray(encrypted.Substring(0, 32));
byte[] rgbIV = Convert.FromBase64String(strArray[1]);
byte[] cipherBytes = HexStringToByteArray(strArray[2]);
// setup the decrypter
SecureString str = new SecureString();
SymmetricAlgorithm algorithm = SymmetricAlgorithm.Create();
ICryptoTransform transform = algorithm.CreateDecryptor(key, rgbIV);
using (var stream = new CryptoStream(new MemoryStream(cipherBytes), transform, CryptoStreamMode.Read))
{
// using this silly loop format to loop one char at a time
// so we never store the entire password naked in memory
int numRed = 0;
byte[] buffer = new byte[2]; // two bytes per unicode char
while( (numRed = stream.Read(buffer, 0, buffer.Length)) > 0 )
{
str.AppendChar(Encoding.Unicode.GetString(buffer).ToCharArray()[0]);
}
}
//
// non-production code
// recover the SecureString; just to check
// from http://stackoverflow.com/questions/818704/how-to-convert-securestring-to-system-string
//
IntPtr valuePtr = IntPtr.Zero;
string secureStringValue = "";
try
{
// get the string back
valuePtr = Marshal.SecureStringToGlobalAllocUnicode(str);
secureStringValue = Marshal.PtrToStringUni(valuePtr);
}
finally
{
Marshal.ZeroFreeGlobalAllocUnicode(valuePtr);
}
return str;
}
// from http://stackoverflow.com/questions/311165/how-do-you-convert-byte-array-to-hexadecimal-string-and-vice-versa
public static byte[] HexStringToByteArray(String hex)
{
int NumberChars = hex.Length;
byte[] bytes = new byte[NumberChars / 2];
for (int i = 0; i < NumberChars; i += 2) bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
public static SecureString DecryptPassword( string psPasswordFile, byte[] key )
{
if( ! File.Exists(psPasswordFile)) throw new ArgumentException("file does not exist: " + psPasswordFile);
string formattedCipherText = File.ReadAllText( psPasswordFile );
return ConvertToSecureString(formattedCipherText, key);
}
According to the docs on ConvertFrom-SecureString the AES encryption algorithm is used:
If an encryption key is specified by using the Key or SecureKey
parameters, the Advanced Encryption Standard (AES) encryption
algorithm is used. The specified key must have a length of 128, 192,
or 256 bits because those are the key lengths supported by the AES
encryption algorithm. If no key is specified, the Windows Data
Protection API (DPAPI) is used to encrypt the standard string
representation.
Look at the DecryptStringFromBytes_Aes example in the MSDN docs.
BTW an easy option would be to use the PowerShell engine from C# to execute the ConvertTo-SecureString cmdlet to do the work. Otherwise, it looks like the initialization vector is embedded somewhere in the ConvertFrom-SecureString output and may or may not be easy to extract.
How do I accomplish the ConvertTo-SecureString -key (1..16) portion in C#/.NET?
Please see the following code:
private static SecureString ConvertToSecureString(string encrypted, string header, byte[] key)
{
string[] strArray = Encoding.Unicode.GetString(Convert.FromBase64String(encrypted.Substring(header.Length, encrypted.Length - header.Length))).Split(new[] {'|'});
SymmetricAlgorithm algorithm = SymmetricAlgorithm.Create();
int num2 = strArray[2].Length/2;
var bytes = new byte[num2];
for (int i = 0; i < num2; i++)
bytes[i] = byte.Parse(strArray[2].Substring(2*i, 2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture);
ICryptoTransform transform = algorithm.CreateDecryptor(key, Convert.FromBase64String(strArray[1]));
using (var stream = new CryptoStream(new MemoryStream(bytes), transform, CryptoStreamMode.Read))
{
var buffer = new byte[bytes.Length];
int num = stream.Read(buffer, 0, buffer.Length);
var data = new byte[num];
for (int i = 0; i < num; i++) data[i] = buffer[i];
var str = new SecureString();
for (int j = 0; j < data.Length/2; j++) str.AppendChar((char) ((data[(2*j) + 1]*0x100) + data[2*j]));
return str;
}
}
Example:
encrypted = "76492d1116743f0423413b16050a5345MgB8ADcAbgBiAGoAVQBCAFIANABNADgAYwBSAEoAQQA1AGQAZgAvAHYAYwAvAHcAPQA9AHwAZAAzADQAYwBhADYAOQAxAGIAZgA2ADgAZgA0AGMANwBjADQAYwBiADkAZgA1ADgAZgBiAGQAMwA3AGQAZgAzAA==";
header = "76492d1116743f0423413b16050a5345";
If you want to get decrypted characters, please check data in the method.
I found the easiest and simplest way was to call the ConvertTo-SecureString PowerShell command directly from C#. That way there's no difference in the implementation and the output is exactly what it would be if you called it from PowerShell directly.
string encryptedPassword = RunPowerShellCommand("\""
+ password
+ "\" | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString", null);
public static string RunPowerShellCommand(string command,
Dictionary<string, object> parameters)
{
using (PowerShell powerShellInstance = PowerShell.Create())
{
// Set up the running of the script
powerShellInstance.AddScript(command);
// Add the parameters
if (parameters != null)
{
foreach (var parameter in parameters)
{
powerShellInstance.AddParameter(parameter.Key, parameter.Value);
}
}
// Run the command
Collection<PSObject> psOutput = powerShellInstance.Invoke();
StringBuilder stringBuilder = new StringBuilder();
if (powerShellInstance.Streams.Error.Count > 0)
{
foreach (var errorMessage in powerShellInstance.Streams.Error)
{
if (errorMessage != null)
{
throw new InvalidOperationException(errorMessage.ToString());
}
}
}
foreach (var outputLine in psOutput)
{
if (outputLine != null)
{
stringBuilder.Append(outputLine);
}
}
return stringBuilder.ToString();
}
}
Adding on to Cheng's answer - I found I had to change:
byte[] magicHeader = HexStringToByteArray(encrypted.Substring(0, 32));
to
byte[] magicHeader = HexStringToByteArray(psProtectedString.Substring(0, 32));
and
SymmetricAlgorithm algorithm = SymmetricAlgorithm.Create();
to
SymmetricAlgorithm algorithm = Aes.Create();
but it otherwise works wonderfully.

Rijndael: same string, different results

We had a small desktop app that needs to be provided as a web feature now (.Net). This app contains some code for encryption and uses Rijndael classes from .Net framework. The code accepts an input string, encrypts it and writes it out the results to a file. Since all the code is contained in one class, I just copied the class to my web service application. When I encrypt the same string, using the same key, in the original app and the new app, the results are different.
The result string given by the original app is a subset of the result string given by my web service. The latter has additional characters at the end of the encrypted string.
Below is the code I am using. Please note that I did not develop this code nor do I understand it fully. Any thoughts on the difference in behaviour? Please help!!
Here is the code that gets the user input and calls the encryptor.
public void EncryptDomain(string EncryptValue, string outputDomainFile)
{
if (EncryptValue.Length > 0)
{
if ((outputDomainFile != null) && (outputDomainFile.Length > 0))
{
_outputDomainFile = outputDomainFile;
}
byte[] input = Encoding.UTF8.GetBytes(EncryptValue);
Transform(input, TransformType.ENCRYPT);
}
This is the encryptor code:
private byte[] Transform(byte[] input, TransformType transformType)
{
CryptoStream cryptoStream = null; // Stream used to encrypt
RijndaelManaged rijndael = null; // Rijndael provider
ICryptoTransform rijndaelTransform = null;// Encrypting object
FileStream fsIn = null; //input file
FileStream fsOut = null; //output file
MemoryStream memStream = null; // Stream to contain data
try
{
// Create the crypto objects
rijndael = new RijndaelManaged();
rijndael.Key = this._Key;
rijndael.IV = this._IV;
rijndael.Padding = PaddingMode.Zeros;
if (transformType == TransformType.ENCRYPT)
{
rijndaelTransform = rijndael.CreateEncryptor();
}
else
{
rijndaelTransform = rijndael.CreateDecryptor();
}
if ((input != null) && (input.Length > 0))
{
//memStream = new MemoryStream();
//string outputDomainFile =
FileStream fsOutDomain = new FileStream(_outputDomainFile,
FileMode.OpenOrCreate, FileAccess.Write);
cryptoStream = new CryptoStream(
fsOutDomain, rijndaelTransform, CryptoStreamMode.Write);
cryptoStream.Write(input, 0, input.Length);
cryptoStream.FlushFinalBlock();
//return memStream.ToArray();
return null;
}
return null;
}
catch (CryptographicException)
{
throw new CryptographicException("Password is invalid. Please verify once again.");
}
finally
{
if (rijndael != null) rijndael.Clear();
if (rijndaelTransform != null) rijndaelTransform.Dispose();
if (cryptoStream != null) cryptoStream.Close();
if (memStream != null) memStream.Close();
if (fsOut != null) fsOut.Close();
if (fsIn != null) fsIn.Close();
}
}
Code that sets up the IV values:
private void GenerateKey(string SecretPhrase)
{
// Initialize internal values
this._Key = new byte[24];
this._IV = new byte[16];
// Perform a hash operation using the phrase. This will
// generate a unique 32 character value to be used as the key.
byte[] bytePhrase = Encoding.ASCII.GetBytes(SecretPhrase);
SHA384Managed sha384 = new SHA384Managed();
sha384.ComputeHash(bytePhrase);
byte[] result = sha384.Hash;
// Transfer the first 24 characters of the hashed value to the key
// and the remaining 8 characters to the intialization vector.
for (int loop = 0; loop < 24; loop++) this._Key[loop] = result[loop];
for (int loop = 24; loop < 40; loop++) this._IV[loop - 24] = result[loop];
}
I would guess that's because of the IV (Initialisation Vector)
This is a classic mistake. Whether you generate an IV yourself or not Rijndael (AES) will provide one for you. The trick is to always save the IV (there's a getter on RijndaelManaged).
When you decrypt, you need to pass both the Key and IV.
If you're saving data to a file or database you can store the IV as a plain text. You can even pass the IV on wire (network, internet) as a plain text. The attacker wont be able(as far as I know) break your cipher based just on an IV. Passing or storing the IV is usually done by prefixing it in front of ciphertext or appending it at the end. (concatenating the two strings)
e.g.
CiphertextIV or IVCiphertext. (remember IV is in plaintext is it should be of a fixed length - making it easy to separate upon receiving for decryption or for database insertion)
So, if your Key is ABCDEFABCDEFABCD and your IV is ABCDEF0123456789
and this plaintext:
'this is some secrect text' (let's say) produces a cipher such as: abcd1234abcd00
You would transmit(or store) like it this: ABCDEF0123456789abcd1234abcd00

Categories

Resources