I am using the following function to encrypt a string:
public string Encrypt(string stringToEncrypt, string SEncryptionKey)
{
try {
key = System.Text.Encoding.UTF8.GetBytes(Strings.Left(SEncryptionKey, 8));
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
byte[] inputByteArray = Encoding.UTF8.GetBytes(stringToEncrypt);
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(key, IV), CryptoStreamMode.Write);
cs.Write(inputByteArray, 0, inputByteArray.Length);
cs.FlushFinalBlock();
return Convert.ToBase64String(ms.ToArray());
} catch (Exception e) {
return e.Message;
}
}
I am wondering if there is some kind of mathematical algorithm that will allow me to determine, in advance, what the length of the Base64 encrypted string length will be. So if my string is 15 characters long, what will the length of the Base64 encrypted string be?
A 15 character string will be at least 15 bytes. It could become 20 or even 30 if you have a lot of non-ASCII characters.
The Encryption will round it up to a multiple of the key block size, lets say 64 bytes.
Then Base64 goes to encode 8 bit bytes into 6 bit tokens, so you get (64 * 8) / 6 tokens (chars).
Yes. You can calculate that.
a) DES encryption uses blocks. As I remember it's 8 bytes. So, if you encrypt anything, it will become rounded to the block sizes.
As example, you encrypt 1 byte, it will be come 8 bytes.
You encrypt 8 bytes, it will become 8 bytes
You encrypt 9 bytes, it will become 16 bytes (minimum amount of block to fit original data)
So, the formale encryptedLength = ceiling (originalLength /8) * 8
b) Base64 has also something very similar to block size. And it's block size is 4. Each original 3 bytes will be converted to 4 bytes block
So, for Base64 encodedLength = ceiling (originalLength/3)*4
So, the final formula is
encodedEncryptedLength = ceiling(ceiling(originalLength/8)*8/3)*4
originalLength here is number of bytes (!!! not characters !!!) in the text which you plan to encrypt.
Related
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×tamp=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×tamp=2022-07-06t11:10:43&expiration=2022-07-06t11:15:43"; // 69 chars-long
secret = "id=jsmith1×tamp=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×tamp=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×tamp=2022-07-06t11:10:43&expiration=2022-07-06t11:15:43";
// Will work:
//$secret = "id=jsmith×tamp=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.
so I ran into the problem that I need to encrypt a string with a size of 450 to 470 bytes. I only have a public key from the server hoster where I want to send an encrypted login message. The problem is the public key is not a standard one, it is modified by the server hoster to meet their cryptosystem. They used Java to do the encryption but I creating a software plugin in c#. So how can I encrypt with their given key my string?
Public key:
48015250010000010100970348B03E911DCCE5ED8F555C2116DBC4D7E96D4C1CDC4BBBAAD26BAA54B5C834F604F9DFB391459459772FB51D00AFD0FE3A9B2DA724E62113A9E8C95BEF377CB5FCF7FEBE42E5282A0DA50F01D5D2635DD958F9836CFB4F8B616777C0CF67DB9A5530AD679E321972E4D4F4F33DED057CB690417A3B42FBFCE2AD9FDD80C815AF1EC858C796D4EA2F17954E4BFAD08E3E0397FA34122AC5951D889B06359A401E5506E50FA176B5A77FAB84E25CFCDBF2330AA173DA1156C8B79D6DB6BFAE828B00811183E63F137648E1FC1786B52D815C248BCADDDF6A17C941414F67A23ADFE82FE76196B64B96E36F8604FA00E8E357F5AE6C83B992D622D5E9CD9C1D00000003010001
They told me their key is separated like that:
48015250 4 bytes "Magic"
01 1 byte Version number
00000101 4 bytes Length of modulus: 0x0101 = 257 bytes
00970348...C1D 257 bytes Modulus
00000003 4 bytes Length of exponent: 0x0003 = 3 bytes
010001 3 bytes Exponent: 0x010001 = 65537
I created a public key in c# with the exponent and modulus from their key
private static string m_strModulus = "00970348B03E911DCCE5ED8F555C2116DBC4D7E96D4C1CDC4BBBAAD26BAA54B5C834F604F9DFB391459459772FB51D00AFD0FE3A9B2DA724E62113A9E8C95BEF377CB5FCF7FEBE42E5282A0DA50F01D5D2635DD958F9836CFB4F8B616777C0CF67DB9A5530AD679E321972E4D4F4F33DED057CB690417A3B42FBFCE2AD9FDD80C815AF1EC858C796D4EA2F17954E4BFAD08E3E0397FA34122AC5951D889B06359A401E5506E50FA176B5A77FAB84E25CFCDBF2330AA173DA1156C8B79D6DB6BFAE828B00811183E63F137648E1FC1786B52D815C248BCADDDF6A17C941414F67A23ADFE82FE76196B64B96E36F8604FA00E8E357F5AE6C83B992D622D5E9CD9C1D";
private static string m_strExponent = "010001";
string xmlPublicKey = "<RSAKeyValue><Modulus>" + m_strModulus + "</Modulus><Exponent>" + m_strExponent + "</Exponent></RSAKeyValue>";
and used RSACryptoServiceProvider.FromXmlString(myOwnPublicXmlKeyGeneratedFromTheirs) to encrypt the login message.
rsa.FromXmlString(xmlPublicKey);
since i have a large string i encrypted the whole thing in blocks of 245 bytes like that:
private const int m_nKeySize = 2048; // The RSA key length
private const int _EncryptionBufferSize = 245;
private const int _DecryptionBufferSize = 256;
using (MemoryStream ms = new MemoryStream())
{
//Create a buffer with the maximum allowed size
byte[] buffer = new byte[_EncryptionBufferSize];
int pos = 0;
int copyLength = buffer.Length;
while (true)
{
//Check if the bytes left to read is smaller than the buffer size, then limit the buffer size to the number of bytes left
if (pos + copyLength > byData.Length)
{
copyLength = byData.Length - pos;
}
//Create a new buffer that has the correct size
buffer = new byte[copyLength];
//Copy as many bytes as the algorithm can handle at a time, iterate until the whole input array is encoded
Array.Copy(byData, pos, buffer, 0, copyLength);
//Start from here in next iteration
pos += copyLength;
//Encrypt the data using the public key and add it to the memory buffer
//_DecryptionBufferSize is the size of the encrypted data
ms.Write(rsa.Encrypt(buffer, false), 0, _DecryptionBufferSize);
//Clear the content of the buffer, otherwise we could end up copying the same data during the last iteration
Array.Clear(buffer, 0, copyLength);
//Check if we have reached the end, then exit
if (pos >= byData.Length)
break;
}
They also mentioned to use the TBC(Trailing Bit Complement) padding algorithm and I didn't find any implementation for this in C# only in java which I cant use.
The server responds me with the error that the public key is wrong or outdated or the syntax is wrong of the message. I can already communicate with the server non encrypted and so the text to encrypt should be fine.
As far as I know, I'm not correctly using their public key because its custom made for their purpose.
Has anybody a clue how to solve this particular case?
Edit:
i converted the hex values of the modulus and the exponent to base64 values, i forgot to mention that. I used the following method:
public static string HexStringtoB64String(string input)
{
return System.Convert.ToBase64String(HexStringToByteArray(input));
}
and used them just before creating the rsa instance like this:
m_strModulus = HexStringtoB64String(m_strModulus);
m_strExponent = HexStringtoB64String(m_strExponent);
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.
How can I take a maximum 19-digits long BigInteger and encrypt it with the following rules:
The result must be based on digits and lower-case English letters only.
All outputs must have the same length to any input. The length must be between 11 to 16 characters, depending on your method, but should be consistent for all possible inputs.
No easy patterns. For example, if you encrypt 000...1 and 000...2 the results should look completely different.
No collisions at all
Should be able to decrypt back to the original BigInteger.
Things that I have tried
Take the original number, XOR it by some key, multiply it by a factor and convert it to a base 36 string. The purpose of the factor is to expand the range so there won't be too much 0 padding. The factor must be between 1 to 36^16/10^19. The problem with this method is that a) it's not 'secure' enough, and b) close numbers have very similar results.
This answer. However, the result was often too short or too long, and the factor method used before didn't work here.
19 digits is slightly less than 64 bits, so you can simply use a 8 byte block cipher like TDEA in ECB mode to encrypt the BigInteger values. First retrieve a default 64 bit encoding of the BigInteger, then encrypt with the secret key, and finally base 36 encode it. The result will be a few characters less than 16 characters, but you can always pad with any value.
Note that if you encrypt the same value twice that you will get the same result, so in that respect the ciphertext does leak some information about the plain text.
The technique you want is format perserving encryption. This will allow you to encrypt a 19 digit number as another 19 digit number.
Unfortunately, the efficient version of this technique is somewhat difficult to implement and in fact can be done very insecurely if you pick the wrong paramaters. There are libraries for it.
This one is open source. It is in C++ unfortunately and its not clear if it runs on windows. Voltage has a library as well, though it presumably costs money and I'm not sure what languages they support.
Here is a piece of code that seems to do it, provided you can convert the BigInteger into an ulong (9999999999999999999 is in fact an ulong). The result is always a fixed 16 characters string (hexadecimal).
byte[] key = // put your 16-bytes private key here
byte[] iv = Guid.NewGuid().ToByteArray(); // or anything that varies and you can carry
string s = EncryptUInt64(ul, key, iv); // encode
ulong dul = DecryptUInt64(s, key, iv).Value; // decode if possible
public static string EncryptUInt64(ulong ul, byte[] key, byte[] iv)
{
using (MemoryStream output = new MemoryStream())
using (var algo = TripleDES.Create())
{
algo.Padding = PaddingMode.None;
using (CryptoStream stream = new CryptoStream(output, algo.CreateEncryptor(key, iv), CryptoStreamMode.Write))
{
byte[] ulb = BitConverter.GetBytes(ul);
stream.Write(ulb, 0, ulb.Length);
}
return BitConverter.ToUInt64(output.ToArray(), 0).ToString("x16");
}
}
public static ulong? DecryptUInt64(string text, byte[] key, byte[] iv)
{
if (text == null)
return null;
ulong ul;
if (!ulong.TryParse(text, NumberStyles.HexNumber, null, out ul))
return null;
using (MemoryStream input = new MemoryStream(BitConverter.GetBytes(ul)))
using (var algo = TripleDES.Create())
{
algo.Padding = PaddingMode.None;
using (CryptoStream stream = new CryptoStream(input, algo.CreateDecryptor(key, iv), CryptoStreamMode.Read))
{
byte[] olb = new byte[8];
try
{
stream.Read(olb, 0, olb.Length);
}
catch
{
return null;
}
return BitConverter.ToUInt64(olb, 0);
}
}
}
I am not familiar with Hashing algorithms and the risks associated when using them and therefore have a question on the answer below that I received on a previous question . . .
Based on the comment that the hash value must, when encoded to ASCII, fit within 16 ASCI characters, the solution is first, to choose some cryptographic hash function (the SHA-2 family includes SHA-256, SHA-384, and SHA-512)
then, to truncate the output of the chosen hash function to 96 bits (12 bytes) - that is, keep the first 12 bytes of the hash function output and discard the remaining bytes
then, to base-64-encode the truncated output to 16 ASCII characters (128 bits)
yielding effectively a 96-bit-strong cryptographic hash.
If I substring the base-64-encoded string to 16 characters is that fundamentally different then keeping the first 12 bytes of the hash function and then base-64-encoding them? If so, could someone please explain (provide example code) for truncating the byte array?
I tested the substring of the full hash value against 36,000+ distinct values and had no collisions. The code below is my current implementation.
Thanks for any help (and clarity) you can provide.
public static byte[] CreateSha256Hash(string data)
{
byte[] dataToHash = (new UnicodeEncoding()).GetBytes(data);
SHA256 shaM = new SHA256Managed();
byte[] hashedData = shaM.ComputeHash(dataToHash);
return hashedData;
}
public override void InputBuffer_ProcessInputRow(InputBufferBuffer Row)
{
byte[] hashedData = CreateSha256Hash(Row.HashString);
string s = Convert.ToBase64String(hashedData, Base64FormattingOptions.None);
Row.HashValue = s.Substring(0, 16);
}
[Original post]
(http://stackoverflow.com/questions/4340471/is-there-a-hash-algorithm-that-produces-a-hash-size-of-64-bits-in-c)
No, there is no difference. However, it's easier to just get the base64 string of the first 12 bytes of the array, instead of truncating the array:
public override void InputBuffer_ProcessInputRow(InputBufferBuffer Row) {
byte[] hashedData = CreateSha256Hash(Row.HashString);
Row.HashValue = Convert.ToBase64String(hashedData, 0, 12);
}
The base 64 encoding simply puts 6 bits in each character, so 3 bytes (24 bits) goes into 4 characters. As long as you are splitting the data at an even 3 byte boundary, it's the same as splitting the string at the even 4 character boundary.
If you try to split the data between these boundaries, the base64 string will be padded with filler data up to the next boundary, so the result would not be the same.
Truncating is as easy as adding Take(12) here:
Change
byte[] hashedData = CreateSha256Hash(Row.HashString);
To:
byte[] hashedData = CreateSha256Hash(Row.HashString).Take(12).ToArray();