Roundtrip Unicode conversion returns different Byte[] array - c#

I'm tinkering with RSA signing of data.
I'm using a plaintext string, which i convert to byte array. i then generate private certificate, sign the byte array and then generate public key.
next i'm using the same byte array to verify the signature.
but i want to convert signature, in between steps, to the string - idea is to append it later on to the file that's being signed.
static void TestSigning(string privateKey)
{
string data = "TEST_TEST-TEST+test+TEst";
Console.WriteLine("==MESSAGE==");
Console.WriteLine(data);
byte[] dataByte = Encoding.Unicode.GetBytes(data);
using (var rsa = new RSACryptoServiceProvider())
{
rsa.FromXmlString(privateKey);
var publicKey = rsa.ToXmlString(false);
byte[] signature = rsa.SignData(dataByte, CryptoConfig.MapNameToOID("SHA512"));
string signatureString = Encoding.Unicode.GetString(signature);
byte[] roundtripSignature = Encoding.Unicode.GetBytes(signatureString);
Console.WriteLine("==TEST==");
Console.WriteLine(signature.Length.ToString());
Console.WriteLine(roundtripSignature.Length.ToString());
using (var checkRSA = new RSACryptoServiceProvider())
{
checkRSA.FromXmlString(publicKey);
bool verification = checkRSA.VerifyData(
dataByte,
CryptoConfig.MapNameToOID("SHA512"),
roundtripSignature);
Console.WriteLine("==Verification==");
Console.WriteLine(verification.ToString());
Console.ReadKey();
}
}
}
now here's the fun part
if i use UTF8 encoding i get byte arrays of different length
256 is the original size
484 is the roundtrip
UTF7 returns different sizes too
256 vs 679
both ASCII and Unicode return proper sizes 256 vs 256.
i've tried using
var sb = new StringBuilder();
for (int i = 0; i < signature.Length; i++)
{
sb.Append(signature[i].ToString("x2"));
}
to get the string. I'm then using Encoding.UTF8.GetBytes() method
this time i get the sizes of:
256 vs 512
if i remove the format from toString() i get:
256 vs 670
signature verification alwayas failed.
it works fine if i use 'signature' instead of roundtripSignature.
my question: Why, despite using same encoding type i get different byte arrays and strings? shouldn't this conversion be lossless?

Unicode isn't a good choice because, at minimum, \0, CR, LF, <delete>, <backspace> (and the rest of the control codes) can mess things up. (See an answer about this for Encrypt/Decrypt for more).
As #JamesKPolk said, you need to use a suitable binary-to-text encoding. Base64 and hex/Base16 are the most common, but there are plenty of other viable choices.

Related

C# Generating PublicKey/IPublicKey object from EC Public key bytes?

When porting a snippet of code from Java to C#, I have come across a specific function which I am struggling to find a solution to. Basically when decoding, an array of bytes from an EC PublicKey needs to be converted to a PublicKey object and everything I have found on the internet doesn't seem to help.
I am developing this on Xamarin.Android using Java.Security libraries and BouncyCastle on Mono 6.12.0.
This is the code I am using in Java:
static PublicKey getPublicKeyFromBytes(byte[] pubKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256r1");
KeyFactory kf = KeyFactory.getInstance("EC", new BouncyCastleProvider());
ECNamedCurveSpec params = new ECNamedCurveSpec("secp256r1", spec.getCurve(), spec.getG(), spec.getN());
ECPoint point = ECPointUtil.decodePoint(params.getCurve(), pubKey);
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, params);
return (ECPublicKey) kf.generatePublic(pubKeySpec);
}
This was the best solution I could come up with which didn't throw any errors in VS. Sadly, it throws an exception and tells me that the spec is wrong:
X9ECParameters curve = CustomNamedCurves.GetByName("secp256r1");
ECDomainParameters domain = new ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H);
ECPoint point = curve.Curve.DecodePoint(pubKey);
ECPublicKeyParameters pubKeySpec = new ECPublicKeyParameters(point, domain);
// Get the encoded representation of the public key
byte[] encodedKey = pubKeySpec.Q.GetEncoded();
// Create a KeyFactory object for EC keys
KeyFactory keyFactory = KeyFactory.GetInstance("EC");
// Generate a PublicKey object from the encoded key data
var pbKey = keyFactory.GeneratePublic(new X509EncodedKeySpec(encodedKey));
I have previously created a PrivateKey in a similar way where I generate a PrivateKey and then export the key in PKCS#8 format, then generating the object from this format. However I couldn't get this to work from an already set array of bytes.
Importing a raw public EC key (e.g. for secp256r1) is possible with pure Xamarin classes, BouncyCastle is not needed for this. The returned key can be used directly when generating the KeyAgreement:
using Java.Security.Spec;
using Java.Security;
using Java.Math;
using Java.Lang;
...
private IPublicKey GetPublicKeyFromBytes(byte[] rawXY) // assuming a valid raw key
{
int size = rawXY.Length / 2;
ECPoint q = new ECPoint(new BigInteger(1, rawXY[0..size]), new BigInteger(1, rawXY[size..]));
AlgorithmParameters algParams = AlgorithmParameters.GetInstance("EC");
algParams.Init(new ECGenParameterSpec("secp256r1"));
ECParameterSpec ecParamSpec = (ECParameterSpec)algParams.GetParameterSpec(Class.FromType(typeof(ECParameterSpec)));
KeyFactory keyFactory = KeyFactory.GetInstance("EC");
return keyFactory.GeneratePublic(new ECPublicKeySpec(q, ecParamSpec));
}
In the above example rawXY is the concatenation of the x and y coordinates of the public key. For secp256r1, both coordinates are 32 bytes each, so the total raw key is 64 bytes.
However, the Java reference code does not import raw keys, but an uncompressed or compressed EC key. The uncompressed key corresponds to the concatenation of x and y coordinate (i.e. the raw key) plus an additional leading 0x04 byte, the compressed key consists of the x coordinate plus a leading 0x02 (for even y) or 0x03 (for odd y) byte.
For secp256r1 the uncompressed key is 65 bytes, the compressed key 33 bytes. A compressed key can be converted to an uncompressed key using BouncyCastle. An uncompressed key is converted to a raw key by removing the leading 0x04 byte.
To apply the above import in the case of an uncompressed or compressed key, it is necessary to convert it to a raw key, which can be done with BouncyCastle, e.g. as follows:
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Crypto.EC;
...
private byte[] ConvertToRaw(byte[] data) // assuming a valid uncompressed (leading 0x04) or compressed (leading 0x02 or 0x03) key
{
if (data[0] != 4)
{
X9ECParameters curve = CustomNamedCurves.GetByName("secp256r1");
Org.BouncyCastle.Math.EC.ECPoint point = curve.Curve.DecodePoint(data).Normalize();
data = point.GetEncoded(false);
}
return data[1..];
}
Test: Import of a compressed key:
using Java.Util;
using Hex = Org.BouncyCastle.Utilities.Encoders.Hex;
...
byte[] compressed = Hex.Decode("023291D3F8734A33BCE3871D236431F2CD09646CB574C64D07FD3168EA07D3DB78");
pubKey = GetPublicKeyFromBytes(ConvertToRaw(compressed));
Console.WriteLine(Base64.GetEncoder().EncodeToString(pubKey.GetEncoded())); // MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMpHT+HNKM7zjhx0jZDHyzQlkbLV0xk0H/TFo6gfT23ish58blPNhYrFI51Q/czvkAwCtLZz/6s1n/M8aA9L1Vg==
As can be easily verified with an ASN.1 parser (e.g. https://lapo.it/asn1js/), the exported X.509/SPKI key MFkw... contains the raw key, i.e. the compressed key was imported correctly.

How to guarantee to the specific endianness for hash computing?

There is the function for sha256 computing:
static string GetHash(string input)
{
byte[] bytes = Encoding.UTF8.GetBytes(input); //1
SHA256 SHA256 = SHA256Managed.Create();
byte[] hashBytes = SHA256.ComputeHash(bytes); //2
var output = BitConverter.ToString(hashBytes); //3
return output;
}
It gets utf8-bytes from c# string, next it computes hash and returns one as string.
I'am confusing about BitConverter. Its ToString(byte[]) method depends on machine architecture (liitle/big endian). My purpose is providing specific endianess (big-endian) for output string.
How can I do it?
I think it can be like:
byte[] bytes = Encoding.UTF8.GetBytes(input); //1
if (BitConverter.IsLittleEndian)
{
Array.Reverse(bytes)
}
//..
But I dont' know how UTF8.GetBytes works (UTF8.GetBytes docs doesn't contains anything about endianness). Is it depending on endianness also? If so I suppose it is right way to reverse array after 1 step, is it?
I think it doesn't matter here because UTF-8 is byte oriented as stated here :
Isn’t on big endian machines UTF-8's byte order different than on little endian machines? So why then doesn’t UTF-8 require a BOM?

Encrypting with DES with user input password

I'm still studying cryptography. I'm trying to create a simple static function in C# that encrypts string to DES (with a Base64 ouput). I learned that DES use 8-Byte as its key. I want the user to input string of any length, use it as the key to encrypt the message, then convert it to Base64. Example is in this site.
public static string EncryptDES(string phrase, string key)
{
string encrypted = "";
byte[] phraseBytes = System.Text.ASCIIEncoding.ASCII.GetBytes(phrase);
byte[] keyBytes = System.Text.Encoding.UTF8.GetBytes(key);
System.Security.Cryptography.MD5CryptoServiceProvider hashMD5Provider
= new System.Security.Cryptography.MD5CryptoServiceProvider();
System.Security.Cryptography.DESCryptoServiceProvider provider
= new System.Security.Cryptography.DESCryptoServiceProvider();
provider.Mode = System.Security.Cryptography.CipherMode.CBC;
System.Security.Cryptography.ICryptoTransform transform
= provider.CreateEncryptor(keyBytes, keyBytes);
System.Security.Cryptography.CryptoStreamMode mode
= System.Security.Cryptography.CryptoStreamMode.Write;
System.IO.MemoryStream memStream = new System.IO.MemoryStream();
System.Security.Cryptography.CryptoStream cryptoStream
= new System.Security.Cryptography.CryptoStream(memStream, transform, mode);
cryptoStream.Write(phraseBytes, 0, phraseBytes.Length);
cryptoStream.FlushFinalBlock();
byte[] encryptedMessageBytes = new byte[memStream.Length];
memStream.Position = 0;
memStream.Read(encryptedMessageBytes, 0, encryptedMessageBytes.Length);
encrypted = System.Convert.ToBase64String(encryptedMessageBytes);
return (encrypted);
} // private static string EncryptDES(string phrase, string key) { }
Then call it like this in Main:
SimpleEncryption.EncryptDES("A message regarding some secure 512-bit encryption", "AnUltimatelyVeryVeryLongPassword");
When a user inputs a random number of string length (whether greater than or less than 8 characters), a cryptographic exception always happens in this line:
System.Security.Cryptography.ICryptoTransform transform = provider.CreateEncryptor(keyBytes, keyBytes);
It says Specified key is not a valid size for this algorithm.
Removing parts of the key to fit in the length of 8 characters (with or without hashing) doesn't seems to be a secure solution (there might be a high rate of collision).
How can I implement DES (not 3DES) with a user input string?
You need to generate a hash from the user's password and take only 8 bytes to use as your key.
var fullHash = hashMD5Provider.ComputeHash(System.Text.Encoding.ASCII.GetBytes(key));
var keyBytes = new byte[8];
Array.Copy(fullHash , keyBytes, 8);
Your question expressed concern about hash collisions from throwing away part of the hash; yes, that certainly does increase the risk, but (assuming your hash algorithm is good) you're no worse off than if you just used a hash algorithm that only produced 8 bytes to begin with. A good hash algorithm should distribute the entropy evenly.

How to encrypt a guid into equal length string in C#?

I need to encrypt a guid and the encrypted string length should be 32 char max, not more than that. Please suggest me an encryption method available in C# for that.
I was using AES in CFB mode, as in Code Project, but that is producing 64 char long.
Well, a GUID is inherently 16 bytes of data... so that's what you should encrypt. That's a single block in AES. As per Reid's comment, the exact size of the output will depend on how you've configured things, but to convert the result into text, you'll probably want to use base64 if you need ASCII text with a fairly minimal size.
Base64 allows you to use 24 bytes to produce a 32 character result - so you should try different padding/cipher modes until you find one where the output is 24 bytes or less, if this 32 character requirement is a "hard" one (and you need ASCII; if you don't need ASCII then there's a lot more room to play...)
If a GUID is 16 bytes (I'll take that as a given) then you can simply do a single AES ECB mode encrypt without padding of the plaintext (i.e. the GUID). You can then convert to hexadecimals. That will with 100% certainty result in a 32 character result.
Note that ECB does not use an IV, which means that you can distinguish different GUID's from each other (as each GUID will be mapped to exactly one ciphertext). But the ciphertext should otherwise simply be identical to the security of the used block cipher and key.
public class EncryptGUI
{
private Aes aes;
public EncryptGUI (byte[] key)
{
aes = Aes.Create ();
aes.Mode = CipherMode.ECB;
aes.Padding = PaddingMode.None;
aes.Key = key;
}
public String encryptUID (byte[] guid)
{
ICryptoTransform aesDecryptor = aes.CreateDecryptor ();
byte[] result = aesDecryptor.TransformFinalBlock (guid, 0, guid.Length);
return ToHex (result);
}
public static string ToHex (byte[] data)
{
StringBuilder hex = new StringBuilder (data.Length * 2);
foreach (byte b in data)
hex.AppendFormat ("{0:x2}", b);
return hex.ToString ();
}
public static void Main (string[] args)
{
byte[] key = new byte[16];
EncryptGUI main = new EncryptGUI (key);
byte[] guid = new byte[16];
Console.Out.WriteLine (main.encryptUID (guid));
}
}

How to retrieve IV from an Encrypted string, then decrypt that string using AES128

I know very little about Encryption, but my goal is to essentially decrypt strings. I have been given the AES(128) key.
However, I must retrieve the IV from the Encrypted string, which is the first 16 bits.
Heres the doc for salesforce for more information (if what i explained was incorrect)
Encrypts the blob clearText using the specified algorithm and private
key. Use this method when you want Salesforce to generate the
initialization vector for you. It is stored as the first 128 bits (16
bytes) of the encrypted blob
http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_classes_restful_crypto.htm (encryptWithManagedIV)
For Retrieving the IV I've tried something like this (I don't believe it's right though):
public string retrieveIv()
{
string iv = "";
string input = "bwZ6nKpBEsuAKM8lDTYH1Yl69KkHN1i3XehALbfgUqY=";
byte[] bytesToEncode = Encoding.UTF8.GetBytes(input);
for(int i = 0; i <= 15; i++){
iv += bytesToEncode[i].ToString(); ;
}
return iv;
}
(Just ignore the fact that the input is hardcoded and not parameterized; easier for testing purposes)
Then use the Best answer from this question to decrypt the string
The IV shouldn't be expressed as a string - it should be as a byte array, as per the AesManaged.IV property.
Also, using Encoding.UTF8 is almost certainly wrong. I suspect you want:
public static byte[] RetrieveIv(string encryptedBase64)
{
// We don't need to base64-decode everything... just 16 bytes-worth
encryptedBase64 = encryptedBase64.Substring(0, 24);
// This will be 18 bytes long (4 characters per 3 bytes)
byte[] encryptedBinary = Convert.FromBase64String(encryptedBase64);
byte[] iv = new byte[16];
Array.Copy(encryptedBinary, 0, iv, 0, 16);
return iv;
}

Categories

Resources