Import a Public key from somewhere else to CngKey? - c#

I am looking for a cross platform way to share public keys for ECDSA signing. I had a great thing going from a performance perspective with CngKey and the standard .NET crypto libraries, but then I couldn't figure out how a 33 (or 65) byte public key (using secp256r1/P256) was getting turned into 104 bytes by MS.. Ergo, I couldn't support cross platform signing and verifying..
I'm using BouncyCastle now, but holy handgranade is it SLOW!
So, looking for suggestions for the following requirements:
Cross platform/Languages (server is .NET, but this is served up via a JSON/Web.API interface)
JavaScript, Ruby, Python, C++ etc..
Not crazy as slow on the server
Not so painfully slow people can't use it on the client.
The client has to be able to sign the message, the server has to be able to validate the signature with a public key that was exchanged at registration to the service.
Anyways, Ideas would be awesome... Thanks

So I have figured out the format of a CngKey exported in ECCPublicKeyBlob and ECCPrivateKeyBlob. This should allow others to interop between other key formats and CngKey for Elliptcal Curve signing and such.
ECCPrivateKeyBlob is formatted (for P256) as follows
[KEY TYPE (4 bytes)][KEY LENGTH (4 bytes)][PUBLIC KEY (64 bytes)][PRIVATE KEY (32 Bytes)]
KEY TYPE in HEX is 45-43-53-32
KEY LENGTH in HEX is 20-00-00-00
PUBLIC KEY is the uncompressed format minus the leading byte (which is always 04 to signify an uncompressed key in other libraries)
ECCPublicKeyBlob is formatted (for P256) as follows
[KEY TYPE (4 bytes)][KEY LENGTH (4 bytes)][PUBLIC KEY (64 bytes)]
KEY TYPE in HEX is 45-43-53-31
KEY LENGTH in HEX is 20-00-00-00
PUBLIC KEY is the uncompressed format minus the leading byte (which is always 04 to signify an uncompressed key in other libraries)
So given a uncompressed Public key in Hex from another language, you can trim the first byte, add those 8 bytes to the front and import it using
CngKey.Import(key,CngKeyBlobFormat.EccPrivateBlob);
Note: The key blob format is documented by Microsoft.
The KEY TYPE and KEY LENGTH are defined in BCRYPT_ECCKEY_BLOB struct as:
{ ulong Magic; ulong cbKey; }
ECC public key memory format:
BCRYPT_ECCKEY_BLOB
BYTE X[cbKey] // Big-endian.
BYTE Y[cbKey] // Big-endian.
ECC private key memory format:
BCRYPT_ECCKEY_BLOB
BYTE X[cbKey] // Big-endian.
BYTE Y[cbKey] // Big-endian.
BYTE d[cbKey] // Big-endian.
The MAGIC values available in .NET are in Microsoft's official GitHub dotnet/corefx BCrypt/Interop.Blobs.
internal enum KeyBlobMagicNumber : int
{
BCRYPT_ECDH_PUBLIC_P256_MAGIC = 0x314B4345,
BCRYPT_ECDH_PRIVATE_P256_MAGIC = 0x324B4345,
BCRYPT_ECDH_PUBLIC_P384_MAGIC = 0x334B4345,
BCRYPT_ECDH_PRIVATE_P384_MAGIC = 0x344B4345,
BCRYPT_ECDH_PUBLIC_P521_MAGIC = 0x354B4345,
BCRYPT_ECDH_PRIVATE_P521_MAGIC = 0x364B4345,
BCRYPT_ECDSA_PUBLIC_P256_MAGIC = 0x31534345,
BCRYPT_ECDSA_PRIVATE_P256_MAGIC = 0x32534345,
BCRYPT_ECDSA_PUBLIC_P384_MAGIC = 0x33534345,
BCRYPT_ECDSA_PRIVATE_P384_MAGIC = 0x34534345
BCRYPT_ECDSA_PUBLIC_P521_MAGIC = 0x35534345,
BCRYPT_ECDSA_PRIVATE_P521_MAGIC = 0x36534345,
...
...
}

Thanks to you I was able to import a ECDSA_P256 public key from a certificate with this code:
private static CngKey ImportCngKeyFromCertificate(X509Certificate2 cert)
{
var keyType = new byte[] {0x45, 0x43, 0x53, 0x31};
var keyLength = new byte[] {0x20, 0x00, 0x00, 0x00};
var key = cert.PublicKey.EncodedKeyValue.RawData.Skip(1);
var keyImport = keyType.Concat(keyLength).Concat(key).ToArray();
var cngKey = CngKey.Import(keyImport, CngKeyBlobFormat.EccPublicBlob);
return cngKey;
}
The 65 byte keys (public key only) start with 0x04 which needs to be removed. Then the header you described is added.
then I was able to verify a signature like that:
var crypto = ECDsaCng(cngKey);
var verify = crypto.VerifyHash(hash, sig);

I just thought I would say thanks to both above posts as it helped me out tremendously. I had to verify a signature using RSA public key using the RSACng object. I was using the RSACryptoServiceProvider before, but that is not FIPS compliant, so I had some problems switching to RSACng. It also requires .NET 4.6. Here is how I got it to work using the above posters as an example:
// This structure is as the header for the CngKey
// all should be byte arrays in Big-Endian order
//typedef struct _BCRYPT_RSAKEY_BLOB {
// ULONG Magic;
// ULONG BitLength;
// ULONG cbPublicExp;
// ULONG cbModulus;
// ULONG cbPrime1; private key only
// ULONG cbPrime2; private key only
//} BCRYPT_RSAKEY_BLOB;
// This is the actual Key Data that is attached to the header
//BCRYPT_RSAKEY_BLOB
// PublicExponent[cbPublicExp]
// Modulus[cbModulus]
//first get the public key from the cert (modulus and exponent)
// not shown
byte[] publicExponent = <your public key exponent>; //Typically equal to from what I've found: {0x01, 0x00, 0x01}
byte[] btMod = <your public key modulus>; //for 128 bytes for 1024 bit key, and 256 bytes for 2048 keys
//BCRYPT_RSAPUBLIC_MAGIC = 0x31415352,
// flip to big-endian
byte[] Magic = new byte[] { 0x52, 0x53, 0x41, 0x31};
// for BitLendth: convert the length of the key's Modulus as a byte array into bits,
// so the size of the key, in bits should be btMod.Length * 8. Convert to a DWord, then flip for Big-Endian
// example 128 bytes = 1024 bits = 0x00000400 = {0x00, 0x00, 0x04, 0x00} = flipped {0x00, 0x04, 0x00, 0x00}
// example 256 bytes = 2048 bits = 0x00000800 = {0x00, 0x00, 0x08, 0x00} = flipped {0x00, 0x08, 0x00, 0x00}
string sHex = (btMod.Length * 8).ToString("X8");
byte[] BitLength = Util.ConvertHexStringToByteArray(sHex);
Array.Reverse(BitLength); //flip to Big-Endian
// same thing for exponent length (in bytes)
sHex = (publicExponent.Length).ToString("X8");
byte[] cbPublicExp = Util.ConvertHexStringToByteArray(sHex);
Array.Reverse(cbPublicExp);
// same thing for modulus length (in bytes)
sHex = (btMod.Length).ToString("X8");
byte[] cbModulus = Util.ConvertHexStringToByteArray(sHex);
Array.Reverse(cbModulus);
// add the 0 bytes for cbPrime1 and cbPrime2 (always zeros for public keys, these are used for private keys, but need to be zero here)
// just make one array with both 4 byte primes as zeros
byte[] cbPrimes = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
//combine all the parts together into the one big byte array in the order the structure
var keyImport = Magic.Concat(BitLength).Concat(cbPublicExp).Concat(cbModulus).Concat(cbPrimes).Concat(publicExponent).Concat(btMod).ToArray();
var cngKey = CngKey.Import(keyImport, CngKeyBlobFormat.GenericPublicBlob);
// pass the key to the class constructor
RSACng rsa = new RSACng(cngKey);
//verify: our randomly generated M (message) used to create the signature (not shown), the signature, enum for SHA256, padding
verified = rsa.VerifyData(M, signature, HashAlgorithmName.SHA256,RSASignaturePadding.Pkcs1);
Note: The sign byte for the modulus (0x00) can either be included in the modulus or not, so the length will be one bigger if it is included. CNGkey seems to handle it ok either way.

You convert EC key to BCRYPT_ECCKEY_BLOB by like this. We should ignore the first byte from EC key because it just represent compressed/uncompressed format.
BCRYPT_ECCKEY_BLOB eccBlobHeader;
PCHAR bycrtptKey;
eccBlobHeader.dwMagic = BCRYPT_ECDH_PUBLIC_P384_MAGIC;
eccBlobHeader.cbKey = 48;//size of EC key(without 1st byte)
memcpy(bycrtptKey, &eccBlobHeader, 8);//copying 8bytes header blob
memcpy(bycrtptKey+ 8,publicKeyFromOtherParty+1,publicKeyFromOtherPartySize- 1);
now use bycrtptKey for importing.

Related

Need PHP 8 version of C#/.NET encryption code

An encryption C# code that has been in use for many years now needs to be converted to PHP 8.
I came close, and there's one remaining issue as described below:
For example, the secret below is longer than 71 characters and it is not encrypted correctly:
secret = "id=jsmith12&timestamp=2022-07-06t11:10:43&expiration=2022-07-06t11:15:43"; //71 chars-long
However, these secrets will be encrypted correctly, since they are less than 71 chars long:
secret = "id=jsmith&timestamp=2022-07-06t11:10:43&expiration=2022-07-06t11:15:43"; // 69 chars-long
secret = "id=jsmith1&timestamp=2022-07-06t11:10:43&expiration=2022-07-06t11:15:43"; // 70 chars-long
There is an online page where you can test if the generated token is correct: https://www.mybudgetpak.com/SSOTest/
You can evaluate the token by providing the generated token, the key, and the encryption method (Rijndael or Triple DES).
If the evaluation (decryption of the token) is successful, the test page will diplay the id, timestamp and expiration values
used in the secret.
C# Code:
The secret, a concatenated query string values, what needs to be encrypted:
string secret = "id=jsmith123&timestamp=2022-07-06t11:10:43&expiration=2022-07-06t11:15:43";
The key:
string key = "C000000000000000"; //16 character-long
ASCII encoded secret and key converted to byte array:
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
byte[] encodedSecret = encoding.GetBytes(secret);
byte[] encodedKey = encoding.GetBytes(key);
Option 1: Rijndael
// Call the generate token method:
string token = GenerateRijndaelSecureToken(encodedSecret, encodedKey);
private string GenerateRijndaelSecureToken(byte[] encodedSecret, byte[] encodedKey)
{
Rijndael rijndael = Rijndael.Create();
// the encodedKey must be a valid length so we pad it until it is (it checks // number of bits)
while (encodedKey.Length * 8 < rijndael.KeySize)
{
byte[] tmp = new byte[encodedKey.Length + 1];
encodedKey.CopyTo(tmp, 0);
tmp[tmp.Length - 1] = (byte)'\0';
encodedKey = tmp;
}
rijndael.Key = encodedKey;
rijndael.Mode = CipherMode.ECB;
rijndael.Padding = PaddingMode.Zeros;
ICryptoTransform ict = rijndael.CreateEncryptor();
byte[] result = ict.TransformFinalBlock(encodedSecret, 0, encodedSecret.Length);
// convert the encodedSecret to a Base64 string to return
return Convert.ToBase64String(result);
}
Option 2: Triple DES
// Call the generate token method:
string token = GenerateSecureTripleDesToken(encodedSecret, encodedKey);
private string generateSecureTripleDesToken(byte[] encodedSecret, byte[] encodedKey)
{
// Generate the secure token (this implementation uses 3DES)
TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();
// the encodedKey must be a valid length so we pad it until it is (it checks // number of bits)
while (encodedKey.Length * 8 < tdes.KeySize)
{
byte[] tmp = new byte[encodedKey.Length + 1];
encodedKey.CopyTo(tmp, 0);
tmp[tmp.Length - 1] = (byte) '\0';
encodedKey = tmp;
}
tdes.Key = encodedKey;
tdes.Mode = CipherMode.ECB;
tdes.Padding = PaddingMode.Zeros;
ICryptoTransform ict = tdes.CreateEncryptor();
byte[] result = ict.TransformFinalBlock(encodedSecret, 0, encodedSecret.Length);
// convert the encodedSecret to a Base64 string to return
return Convert.ToBase64String(result);
}
PHP 8 code:
public $cipher_method = "AES-256-ECB";
// Will not work:
//$secret = "id=jsmith12&timestamp=2022-07-06t11:10:43&expiration=2022-07-06t11:15:43";
// Will work:
//$secret = "id=jsmith&timestamp=2022-07-06t11:10:43&expiration=2022-07-06t11:15:43";
$key = "C000000000000000";
$token = openssl_encrypt($secret, $cipher_method, $key);
There are two things to be aware of:
The C# code pads the key with 0x00 values to the required length, i.e. 256 bits for AES-256 and 192 bits for 3DES. Since PHP/OpenSSL automatically pads keys that are too short with 0x00 values, this does not need to be implemented explicitly in the PHP code (although it would be more transparent).
The C# code uses Zero padding. PHP/OpenSSL on the other hand applies PKCS#7 padding. Since PHP/OpenSSL does not support Zero padding, the default PKCS#7 padding must be disabled with OPENSSL_ZERO_PADDING (note: this does not enable Zero padding, the name of the flag is poorly chosen) and Zero padding must be explicitly implemented, e.g. with:
function zeropad($data, $bs) {
$length = ($bs - strlen($data) % $bs) % $bs;
return $data . str_repeat("\0", $length);
}
Here $bs is the block size (16 bytes for AES and 8 bytes for DES/3DES).
Further changes are not necessary! A possible implementation is:
$cipher_method = "aes-256-ecb"; // for AES (32 bytes key)
//$cipher_method = "des-ede3"; // for 3DES (24 bytes key)
// Zero pad plaintext (explicitly)
$bs = 16; // for AES
//$bs = 8; // for 3DES
$secret = zeropad($secret, $bs);
// Zero pad key (implicitly)
$key = "C000000000000000";
$token = openssl_encrypt($secret, $cipher_method, $key, OPENSSL_ZERO_PADDING); // disable PKCS#7 default padding, Base64 encode (implicitly)
print($token . PHP_EOL);
The ciphertexts generated in this way can be decrypted using the linked website (regardless of their length).
The wrong padding causes decryption to fail on the web site (at least to not succeed reliably). However, the logic is not correct that decryption fails only if the plaintext is larger than 71 bytes (even if only the range between 65 and 79 bytes is considered). For example, decryption fails also with 66 bytes. The page source provides a bit more information than the GUI:
Could not read \u0027expiration\u0027 as a date: 2022-07-06t11:15:43\u000e\u000e\u000e\u000e\u000e\u000e\u000e\u000e\u000e\u000e\u000e\u000e\u000e\u000e
The problem is (as expected) the PKCS#7 padding bytes at the end: 14 0x0e values for 66 bytes.
Why decryption works for some padding bytes and not for others can only be reliably answered if the decryption logic of the web site were known. In the end, however, the exact reason doesn't matter.
Note that the applied key expansion is insecure. Also, ECB is insecure, 3DES is outdated, and Zero padding is unreliable.

trying to implement Speck32/64 block Cipher

I am trying to implement Speck 32/64 block Cipher in c# I'm stuck at encryption decryption algorithm. i know that i should split the plain text in to 2 word according to algorithm
x,y = plaintext words
--------------------------- key expansion --------------------------
for i = 0..T-2
[i+m-1] ← (k[i] + S−α
[i]) ⊕ i
k[i+1] ← S
β k[i] ⊕ `[i+m-1]
end for
---------------------------- encryption ----------------------------
for i = 0..T-1
x ← (S−α x + y) ⊕ k[i]
y ← S
βy ⊕ x
end for
References
The SIMON and SPECK Families of Lightweight Block Ciphers
https://eprint.iacr.org/2013/404
my question is the plaintext should be string then i convert to binary or what and use it in the above algo?
the algorithm didnot say the type of plaintext and there is example encryption
Key: 1918 1110 0908 0100
Plaintext: 6574 694c
Ciphertext: a868 42f2
SPECK 32/64 cipher expects 4 bytes as the input.
Plaintext: 6574 694c
means
byte[] plaintext = new byte[] {0x65, 0x74, 0x69, 0x4C};
where each byte is specified as hexadecimal value using the 0x prefix.
You will divide the plaintext in the first step:
byte[] x = new byte[] {plaintext[0], plaintext[1]};
byte[] y = new byte[] {plaintext[2], plaintext[3]};
Note: use some more clever array manipulation to speed up your cipher, the example above is for educational purposes only.
Note 2: handling input as a uint might be a good approach, it could be much faster than arrays with a little of bitwise magic:
uint plaintext = 0x6574694C;
ushort x = (ushort) (plaintext >> 16);
ushort y = (ushort) plaintext;

Function to XOR two 128 bits. How do I generate 128 bit values?

I'm trying to learn simple cryptology and as a starter I'm trying to achieve the following.
A function, taking two 128 bit params (key and plaintext) as input and returning their XOR. I know XOR is not safe but I'm starting out with a simple example.
This is what I have tried:
class Program
static void Main(string[] args)
{
string key = "B25829846AED8"; //128 bits??
string plaintext = "A9BB51625ECBE"; //128 bits??
//Convert key to byte array
byte[] keyBytes = new byte[key.Length * sizeof(char)];
System.Buffer.BlockCopy(key.ToCharArray(), 0, keyBytes, 0, keyBytes.Length);
//Convert plaintext to byte array
byte[] plaintextBytes = new byte[plaintext.Length * sizeof(char)];
System.Buffer.BlockCopy(plaintext.ToCharArray(), 0, plaintextBytes, 0, plaintextBytes.Length);
//Encrypt (XOR)
string result = new Encrypter().encrypt(keyBytes, plaintextBytes);
}
}
Encrypter.cs :
class Encrypter
{
public string encrypt(byte[] key, byte[] plaintext)
{
BitArray keyBits = new BitArray(key);
BitArray plaintextBits = new BitArray(plaintext);
if(keyBits.Length == plaintextBits.Length)
{
BitArray result = keyBits.Xor(plaintextBits);
return result.ToString();
}
return null;
}
}
My problem:
I'm struggling with what to put as the key and plaintext. How can I ensure that the values are exactly 128 bit each?
E.g. B25829846AED8 is apparently a 128 bit WEP key. If I assign this to my key variable and when I enter the encrypt method the keyBits.Length property has the value 208. This is what I don't get. Also the parameter key has the length 26, which I'm also confused by.
Why is the key-length 26?
C#-strings is in unicode, so you can write all characters out there eg. Chinese, Japanese, etc. etc. That takes two bytes (in utf-16). 13*2=26.
Is your wep-key 128 bits
You've got a key for 128 bit wep-protocoll which is using 104 bit keys. (fun times) Wiki on Wep
But as far as I understand you're not trying to implement wep, you're trying to encode something. Take two random integers translate them to bytes and put them after each other. BAM- 128 bits :)
using System.Linq
byte[] key = BitConverter.GetBytes(25).Concat(BitConverter.GetBytes(284)) ;
Other than that you seam to have it under control, good luck :)
You want to use 128 bit keys or in other words 16 bytes. Strings are made of chars and the char datatype in C# is uses 2 bytes (16 bits). So you could make a 16 byte key from strings of length 8, which is sort of problematic because it is difficult to use the full 128bit range due to unprintable characters and so on. It would be way easier to represent the key as a byte array with length 16 from the start for example: byte[] key = {1, 8, 255, 12, 2, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5, 5};
You say that B25829846AED8 is a 128bit key. Interpreted as a string this is not true: 13 chars = 26 bytes = 208 bit so that is the explanation for your result. Interpreting each character as a hexadecimal digit this key would be 13*4 = 52bit. Interpreting each character as a ANSI character (size 8bit) would be 13*8 = 104 bit.
So to produce the byte array for the key from a string or number you have to define how you interpret the string or number. As already said above, easiest would be to enter 16 bytes directly.

RSA Extra Bit 129 instead off 128

I am using this function to change public key and encrypt data:
public byte[] EncryptData(byte[] data2Encrypt)
{
string key = "109120132967399429278860960508995541528237502902798129123468757937266291492576446330739696001110603907230888610072655818825358503429057592827629436413108566029093628212635953836686562675849720620786279431090218017681061521755056710823876476444260558147179707119674283982419152118103759076030616683978566631413";
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(1024);
BigInteger intk;
BigInteger.TryParse(key, out intk);
RSAParameters privateKey = new RSAParameters();
byte[] expont = { 1, 0, 1 };
byte[] modulus = intk.ToByteArray();
Logger.log(Log_Type.ERROR, "Pierwszy bit: " + modulus[0]);
privateKey.Exponent = expont;
privateKey.Modulus = intk.ToByteArray();
rsa.ImportParameters(privateKey);
return rsa.Encrypt(data2Encrypt, false);
}
But it return me array with 129 length instead od 128 (What should be max lenght using 1024 bits i think). What can be a reason?
If you use BigInteger an additional bit is always placed before the
number. If your key has 1024 bits you get 1025 bits, so skip the
first byte if it is 0x00 (meaning a positive value)
BigInteger produces signed little-endian numbers, while RSAParameters requires unsigned big-endian. You can still use BigInteger though, just convert its output to what RSAParameters is expecting.
byte[] modulus = intk.ToByteArray().Reverse().Skip(1).ToArray();
Reverse to make the number big-endian, and Skip(1) to skip the sign.
I am not sure, that it should be even converted into BitInteger. RSA key what I am trying to get is similar to this function in C++
void Crypt::rsaSetPublicKey(const std::string& n, const std::string& e)
{
BN_dec2bn(&m_rsa->n, n.c_str());
BN_dec2bn(&m_rsa->e, e.c_str());
// clear rsa cache
if(m_rsa->_method_mod_n) { BN_MONT_CTX_free(m_rsa->_method_mod_n); m_rsa->_method_mod_n = NULL; }
}
Where 'n' is this key srting and 'e' is : "65537"
If it should not be a BigInteger, then what?

Two-key triple DES encryption solution needed

Is this possible in C#? How would I accomplish this?
Two-key triple DES is where we encrypt with K1, then decrypt with K2 and finally encrypt again with K1. The keyspace is thus 2 x 56 = 112 bits.
For example, with K1=0x0123456789ABCDEF and K2=0xFEDCBA9876543210 you would set the triple DES key to be 0x0123456789ABCDEFFEDCBA98765432100123456789ABCDEF.
0123456789ABCDEF FEDCBA9876543210 0123456789ABCDEF
|<------K1------>|<------K2------>|<------K3------>|
It accepts A9993E364706816A and the 2 keys that it must use is K1 = 0123456789ABCDEF and K2 = FEDCBA9876543210. The end result must be: 6E5271A3F3F5C418 which I am not getting.
UPDATE:
I am trying to create the concatenated key that I need to use. The 2 keys used above is converted to a byte array and seems to have a length of 16 each. And when the 2 are concatenated then the length is 32. Then my code bombs out. The key has to have a length of 16 or 24. What do I need to do in this case?
UTF8Encoding characterEncoding = new UTF8Encoding();
byte[] accessKey1ByteArray = characterEncoding.GetBytes(accessKey1);
byte[] accessKey2ByteArray = characterEncoding.GetBytes(accessKey2);
byte[] accessKeysArray = accessKey1ByteArray.Concat(accessKey2ByteArray).ToArray();
Here is where I try to set my values:
public byte[] ComputeTripleDesEncryption(byte[] plainText, byte[] key)
{
TripleDESCryptoServiceProvider des = new TripleDESCryptoServiceProvider();
des.Key = key;
des.GenerateIV();
des.Mode = CipherMode.ECB;
des.Padding = PaddingMode.None;
ICryptoTransform ic = des.CreateEncryptor();
byte[] enc = ic.TransformFinalBlock(plainText, 0, plainText.Length);
return enc;
}
UPDATE 2
Do I need to set the size? The byte array key that I am sending through is K1 + K2 + K1.
The text that I am sending through, do I need convert this to bytes like what you recommended, or can the following also do the trick?
UTF8Encoding characterEncoding = new UTF8Encoding();
byte[] block1ByteArray = characterEncoding.GetBytes(block1);
The value of block1 is: A9993E364706816A.
How I got A9993E364706816A was from my SHA-1 hashed result. The first 16 characters of this hashed result of my string that I want to encode.
This sounds like you just want to set a 128 bit key for the triple des key.
I believe in this case if you provide a 128 bit key it splits it into two 64 bit keys and uses the first as K1 and K3 and the second as K2 which is exactly what you want.
Unfortunately I can't find a source to quote on this but I did a lot of reading on the subject recently when implementing some crypto stuff myself and finding out about key lengths and this is what I discovered.
If you have K1 and K2 as byte arrays already then you should be able to just use a nice little linq extension method and do:
SymmetricAlgorithm cryptoService = new TripleDESCryptoServiceProvider();
byte[] myKey = K1.Concat(K2).ToArray();
cryptoService.Key = mKey;
That will then do as you want.
In response to your updated part of the question the two keys you have are hexdecimal representations of a sequence of bytes. 0x0123456789ABCDEF is 16 characters of hexdecimal but this is equivalent to 8 bytes of information since it is 4 bits in each character - two making up a byte.
To convert that string to a byte array the following function can be used:
public static byte[] StringToByteArray(String hex)
{
if (hex.Substring(0,2)=="0x")
hex = hex.Substring(2);
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;
}
(From How do you convert Byte Array to Hexadecimal String, and vice versa?)
This will then be used like this:
string K1="0x0123456789ABCDEF";
string K2="0xFEDCBA9876543210";
byte[] key = StringToByteArray(K1).Concat(StringToByteArray(K2)).ToArray();
When implementing TDES you will need to agree a key, Block Cipher mode, Padding method and in most Block modes you will need an initialisation Vector. You'll possibly also want to use a Message Authentication Code.
To get an initialisation vector you'll want to do something like:
cryptoService.GenerateIV();
byte[] iv = cryptoService.IV;
I strongly advise reading pages on encryption to understand better what the various things you are doing actually are rather than just writing the code. It will make you more confident in your security and make you sound more confident while dealing with others. I've taken the liberty of including some links, most of which can be found by just googling.
Useful links:
http://en.wikipedia.org/wiki/Initialization_vector - all about initialisation vectors
http://en.wikipedia.org/wiki/Triple_DES - on the TDES algorithm
http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation - How consecutive blocks of data interact with each other.
http://en.wikipedia.org/wiki/Padding_%28cryptography%29 - Not massively important except there are different ways of padding and both sides need to be using the same one (of course).
http://chargen.matasano.com/chargen/2009/7/22/if-youre-typing-the-letters-a-e-s-into-your-code-youre-doing.html - An excellent and amusing commentary on the use of encryption and where there are weaknesses and what encryption can and cannot do.
http://en.wikipedia.org/wiki/Message_authentication_code - How to confirm that your message hasn't been tampered with
To encrypt/decrypt data with the TripleDES algorithm, you can use the TripleDESCryptoServiceProvider Class. The algorithm supports key lengths from 128 bits to 192 bits in increments of 64 bits.
If you have two 64-bit keys
byte[] k1 = new byte[] { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF };
byte[] k2 = new byte[] { 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10 };
and want to concatenate k1, k2 and k1 again to a 192-bit key, you can do this as follows:
byte[] key = new byte[K1.Length + K2.Length + K1.Length];
Buffer.BlockCopy(k1, 0, result, 0, 8);
Buffer.BlockCopy(k2, 0, result, 8, 8);
Buffer.BlockCopy(k1, 0, result, 16, 8);
Note that, in addition to the key, you also need an initialization vector:
byte[] iv = // ...
Example:
byte[] data = new byte[] { 0xA9, 0x99, 0x3E, 0x36, 0x47, 0x06, 0x81, 0x6A };
using (var csp = new TripleDESCryptoServiceProvider())
using (var enc = csp.CreateEncryptor(key, iv))
using (var stream = new MemoryStream())
using (var crypto = new CryptoStream(stream, enc, CryptoStreamMode.Write))
{
crypto.Write(data, 0, data.Length);
}

Categories