There might be just a minor mistake I have done that I cannot see. Perhaps someone else looking over this could figure out what I am doing wrong.
This is function in C# that I am trying to rewrite in Go, the objective is to output the same value when calling a function.
public static string NewEncrypt(string Input)
{
RijndaelManaged rijndaelManaged = new RijndaelManaged();
rijndaelManaged.KeySize = 256;
rijndaelManaged.BlockSize = 256;
rijndaelManaged.Padding = PaddingMode.PKCS7;
rijndaelManaged.Key = Convert.FromBase64String(Convert.ToBase64String(Encoding.UTF8.GetBytes("095fc90fe8b18e8f243e4b07a9c0d170")));
rijndaelManaged.IV = Convert.FromBase64String(Convert.ToBase64String(Encoding.UTF8.GetBytes("8bef55a546d27958ead1fdddba4d36ea")));
ICryptoTransform transform = rijndaelManaged.CreateEncryptor(rijndaelManaged.Key, rijndaelManaged.IV);
byte[] myArray = null;
using (MemoryStream memoryStream = new MemoryStream())
{
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Write))
{
byte[] bytes = Encoding.UTF8.GetBytes(Input);
cryptoStream.Write(bytes, 0, bytes.Length);
}
myArray = memoryStream.ToArray();
}
return Convert.ToBase64String(myArray);
}
You can call it using:
NewEncrypt("{\"randJsonList\":[ \"abc\" ], \"name\":\"baron\", \"support\":\"king\"}")
We have this return output (myArray):
DdSUyoYRYW/zDNSVaA1JZ39WqJt06qp0FiJUlCW5BbZWEt41GzsmtgVnGZuHigZNs7qKhI+kHAKMXL8EPnK1vg==
Now for my Go implementation (I was trying to make use of GitHub resources: https://gist.github.com/huyinghuan/7bf174017bf54efb91ece04a48589b22):
First thing you might notice is that I do not know where I can use global IV variable, each time you run this code you are presented with different output. I want to output the same result as in C#, unless the input string is modified
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"io"
)
var KEY = []byte("095fc90fe8b18e8f243e4b07a9c0d170")
var IV = []byte("8bef55a546d27958ead1fdddba4d36ea")
func encrypt(myInput string) []byte {
plaintext := []byte(myInput)
// Create the AES cipher
block, err := aes.NewCipher(KEY)
if err != nil {
panic(err)
}
plaintext, _ = pkcs7Pad(plaintext, block.BlockSize())
// The IV needs to be unique, but not secure. Therefore it's common to
// include it at the beginning of the ciphertext.
ciphertext := make([]byte, aes.BlockSize+len(plaintext))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
panic(err)
}
bm := cipher.NewCBCEncrypter(block, iv)
bm.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)
//stream := cipher.NewCFBEncrypter(block, iv)
//stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext)
return ciphertext
}
// pkcs7Pad right-pads the given byte slice with 1 to n bytes, where
// n is the block size. The size of the result is x times n, where x
// is at least 1.
func pkcs7Pad(b []byte, blocksize int) ([]byte, error) {
if blocksize <= 0 {
return nil, errors.New("invalid blocksize")
}
if b == nil || len(b) == 0 {
return nil, errors.New("invalid PKCS7 data (empty or not padded)")
}
n := blocksize - (len(b) % blocksize)
pb := make([]byte, len(b)+n)
copy(pb, b)
copy(pb[len(b):], bytes.Repeat([]byte{byte(n)}, n))
return pb, nil
}
func main() {
plainText := "{\"randJsonList\":[ \"abc\" ], \"name\":\"baron\", \"support\":\"king\"}"
x := encrypt(plainText)
outputString := base64.StdEncoding.EncodeToString(x)
fmt.Println(outputString)
}
Example output (not the same as C#):
PS D:\Software\Git\repositories\tet> go run .\main.go
N+hm5TItq367eXAz+WbtKXhhhMAy4woEKSngTf6rGUt8GZce7LsUxaqNtheceGDZ2dK8Bx187x87NeRPC1UQ6lUokjy7t1MLU8NcCtjODCM=
PS D:\Software\Git\repositories\tet> go run .\main.go
OT/CngTVs2O4BR4czjvR3MLVPoKFH2dUtW8LsIDUgLXfikJrRKsvKGaf0JFe39Cwf1/00HP7mvmCure7+IO+vupzAtdLX6nTQt1KZGsNp4o=
PS D:\Software\Git\repositories\tet> go run .\main.go
yDRHxWTvjX4HnSW8jbao+0Mhf77zgRj9tKXA3MNtAoF1I3bRou5Sv4Ds+r0HRuiA7NkoBR57m4aCYcU6quYzQA3R0GCGB8TGUfrWS5PvMNU=
The C# code uses Rijndael with a block size of 256 bits (see comments), a static IV (see comments) and returns only the ciphertext (i.e. without prepended IV).
The Go code applies AES with by definition a block size of 128 bits, a randomly generated IV (the static IV in the code is ignored) and returns the concatenation of IV and ciphertext.
AES is a subset of Rijndael. Rijndael defines different block sizes and key sizes between 128 and 256 bits in 32 bit steps, see here. For AES only the block size 128 bits is defined and the key sizes 128, 192 and 256 bits. Note that the standard is AES and not Rijndael, so AES should be preferred over Rijndael (many libraries do not even implement Rijndael, but AES instead).
A static IV is insecure. Key/IV pairs must not repeat for security reasons. Therefore, in general, with a fixed key, a random IV is generated for each encryption. The IV is not secret and is passed along with the ciphertext to the decryption side, typically concatenated in the order IV|ciphertext.
Therefore, the current Go code is a secure implementation (even more secure is authenticated encryption e.g. via GCM), while the C# code is not. So it would make more sense to modify the C# code to be functionally equivalent to the Go code.
However, since the C# code seems to be the reference, the following changes are needed in the Go code to make it functionally identical to the C# code:
Instead of AES, Rijndael must be applied. In the following example, pkg.go.dev/github.com/azihsoyn/rijndael256 is used. To do this, import "github.com/azihsoyn/rijndael256" and formally replace aes with rijndael256. You can of course apply another implementation.
The static IV is to be applied: bm := cipher.NewCBCEncrypter(block, IV).
iv and its filling is to be removed together with associated imports.
Only the ciphertext is returned in the enecrypt()-method: return ciphertext[rijndael256.BlockSize:].
The following Go code gives the result of the C# code:
package main
import (
"bytes"
"github.com/azihsoyn/rijndael256"
"crypto/cipher"
"encoding/base64"
"errors"
"fmt"
)
var KEY = []byte("095fc90fe8b18e8f243e4b07a9c0d170")
var IV = []byte("8bef55a546d27958ead1fdddba4d36ea")
func encrypt(myInput string) []byte {
plaintext := []byte(myInput)
// Create the AES cipher
block, err := rijndael256.NewCipher(KEY)
if err != nil {
panic(err)
}
plaintext, _ = pkcs7Pad(plaintext, block.BlockSize())
// The IV needs to be unique, but not secure. Therefore it's common to
// include it at the beginning of the ciphertext.
ciphertext := make([]byte, rijndael256.BlockSize+len(plaintext))
bm := cipher.NewCBCEncrypter(block, IV)
bm.CryptBlocks(ciphertext[rijndael256.BlockSize:], plaintext)
return ciphertext[rijndael256.BlockSize:]
}
// pkcs7Pad right-pads the given byte slice with 1 to n bytes, where
// n is the block size. The size of the result is x times n, where x
// is at least 1.
func pkcs7Pad(b []byte, blocksize int) ([]byte, error) {
if blocksize <= 0 {
return nil, errors.New("invalid blocksize")
}
if b == nil || len(b) == 0 {
return nil, errors.New("invalid PKCS7 data (empty or not padded)")
}
n := blocksize - (len(b) % blocksize)
pb := make([]byte, len(b)+n)
copy(pb, b)
copy(pb[len(b):], bytes.Repeat([]byte{byte(n)}, n))
return pb, nil
}
func main() {
plainText := "{\"randJsonList\":[ \"abc\" ], \"name\":\"baron\", \"support\":\"king\"}"
x := encrypt(plainText)
outputString := base64.StdEncoding.EncodeToString(x)
fmt.Println(outputString)
}
The output:
DdSUyoYRYW/zDNSVaA1JZ39WqJt06qp0FiJUlCW5BbZWEt41GzsmtgVnGZuHigZNs7qKhI+kHAKMXL8EPnK1vg==
is equal to that of the C# code.
Related
I have encrypted a string using EasyCrypto in C# using the following code
Encryption C#:
/*
EasyCrypto encrypted key format from CryptoContainer.cs file from the EasyCrypto source on GitHub.
* Format:
* 04 bytes 00 - MagicNumber
* 02 bytes 04 - DataVersionNumber
* 02 bytes 06 - MinCompatibleDataVersionNumber
* 16 bytes 08 - IV
* 32 bytes 24 - Salt
* 19 bytes 56 - Key check value
* 48 bytes 75 - MAC
* 04 bytes 123 - Additional header data length
* xx bytes 127 - Additional data
* ----- end of header ----- (sum: 127)
* xx bytes - additional header data (0 for version 1)
* xx bytes - data
*/
AesEncryption.EncryptWithPassword("data to encrypt", "password string");
/*
Method Description:
Encrypts string and returns string. Salt and IV will be embedded to encrypted string. Can later be decrypted with
EasyCrypto.AesEncryption.DecryptWithPassword(System.String,System.String,EasyCrypto.ReportAndCancellationToken)
IV and salt are generated by EasyCrypto.CryptoRandom which is using System.Security.Cryptography.Rfc2898DeriveBytes.
IV size is 16 bytes (128 bits) and key size will be 32 bytes (256 bits).
/*
I am trying to decrypt in C++ using Crypto++, using the following code. I am just getting the error "ciphertext length is not a multiple of block size", what is the missing part in the code? any help would be highly appreciable.
Decryption C++:
string Decrypt() {
// getting CryptoPP::byte array from passowrd
string destination;
CryptoPP::StringSource ss(<hex of password string>, true, new CryptoPP::HexDecoder(new CryptoPP::StringSink(destination)));
CryptoPP::byte* keyByteArray = (CryptoPP::byte*)destination.data();
// getting CryptoPP::byte array from encoded data
string pkDst;
CryptoPP::StringSource ss2(<hex of encoded data>, true, new CryptoPP::HexDecoder(new CryptoPP::StringSink(pkDst)));
CryptoPP::byte* pkByteArray = (CryptoPP::byte*)pkDst.data();
// getting initialization vector from encoded data
CryptoPP::byte iv[16];
for (int i = 8; i < 24; i++) {
iv[i] = pkByteArray[i];
}
string result = CBCMode_Decrypt(keyByteArray, 32, iv);
return result;
}
string CBCMode_Decrypt(CryptoPP::byte key[], int keySize, CryptoPP::byte iv[]) {
string recovered = "";
//Decryption
try
{
CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption d;
d.SetKeyWithIV(key, keySize, iv);
// The StreamTransformationFilter removes
// padding as required.
CryptoPP::StringSource s("encoded string", true, new CryptoPP::StreamTransformationFilter(d, new CryptoPP::StringSink(recovered))); // StringSource
}
catch (const CryptoPP::Exception& e)
{
cerr << e.what() << endl;
exit(1);
}
return recovered;
}
In the Crypto++ code, the following steps must be performed for decryption:
Base64 decoding of the EasyCrypto data
Separating IV, salt and ciphertext (using the information from the CryptoContainer.cs file)
Deriving the 32 bytes key via PBKDF2 using salt and password (digest: SHA-1, iteration count: 25000)
Decryption with AES-256 in CBC mode and PKCS#7 padding (using key and IV)
A possible Crypto++ implementation is:
#include "aes.h"
#include "modes.h"
#include "pwdbased.h"
#include "sha.h"
#include "base64.h"
using namespace CryptoPP;
using namespace std;
...
// Base64 decode data from EasyCrypto
string encoded = "bqCrDAQABABtXsh2DxqYdpZc6M6+kGALOsKUHzxoMR6WAVg5Qtj3zWbr4MiEBdqt9nPIiIZAynFAZmweHQPa/PhEItR6M8Jg1bHAYeQ8Cm5eUlKNzPXFNfuUw0+qtds29S0L4wAWY0xfuiBJTUeTJuSLWqoirm/rHGOWAAAAAKtBivUDvxta1d0QXE6J9x5VdSpAw2LIlXARKzmz+JRDtJcaj4KmGmXW/1GjZlMiUA==";
string decoded;
StringSource ssB64(
encoded,
true,
new Base64Decoder(
new StringSink(decoded)
)
);
// Separate IV, salt and ciphertext
string ivStr = decoded.substr(8, 16);
string saltStr = decoded.substr(24, 32);
string ciphertextStr = decoded.substr(127);
// Derive 32 bytes key using PBKDF2
char password[] = "my passphrase";
unsigned int iterations = 25000;
byte key[32];
size_t keyLen = sizeof(key);
PKCS5_PBKDF2_HMAC<SHA1> pbkdf;
pbkdf.DeriveKey(key, keyLen, 0, (byte*)password, sizeof(password), (byte*)saltStr.c_str(), saltStr.length(), iterations, 0.0f);
// Decrypt with AES-256, CBC, PKCS#7 padding
string decrypted;
CBC_Mode<AES>::Decryption decryption(key, keyLen, (byte*)ivStr.c_str());
StringSource ssDec(
ciphertextStr,
true,
new StreamTransformationFilter(
decryption,
new StringSink(decrypted),
BlockPaddingSchemeDef::BlockPaddingScheme::PKCS_PADDING
)
);
// Output
cout << "Decrypted: " << decrypted << "\n";
with the output:
Decrypted: The quick brown fox jumps over the lazy dog
The ciphertext was generated with EasyCrypto:
AesEncryption.EncryptWithPassword("The quick brown fox jumps over the lazy dog", "my passphrase");
The previous section focused on decryption. Note, however, that for security reasons, authentication is required before decryption and decryption may only be performed on successfully authenticated data.
For authentication also the MAC must be determined in addition to IV, salt and ciphertext. EasyCrypto applies an HMAC-SHA-384 as MAC. Only the ciphertext is used to determine the MAC, and the key for authentication is the same as the key for encryption.
For authentication, the calculated and the sent MAC must be compared. If both are the same, the authentication is successful (and the decryption can be performed).
A possible Crypto++ implementation for the authentication is:
// Get the sent MAC
string macSentStr = decoded.substr(75, 48);
// Calculate the MAC using ciphertext and encryption key
string macCalcStr;
HMAC<SHA384> hmac(key, keyLen);
StringSource ssMac(
ciphertextStr,
true,
new HashFilter(hmac,
new StringSink(macCalcStr)
)
);
// Compare both MACs
cout << (!macSentStr.compare(macCalcStr) ? "Authentication successful" : "Authentication failed") << endl; // compare returns 0 if both strings match
which successfully authenticates the sample data.
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.
I'm trying to encrypt a byte array in C# using AES192 and a PBKDF2 password/salt based key and decrypt the same data in NodeJS. However my key generation produces different results in both NodeJS and C#.
The C# code is as follows:
private void getKeyAndIVFromPasswordAndSalt(string password, byte[] salt, SymmetricAlgorithm symmetricAlgorithm, ref byte[] key, ref byte[] iv)
{
Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, salt);
key = rfc2898DeriveBytes.GetBytes(symmetricAlgorithm.KeySize / 8);
iv = rfc2898DeriveBytes.GetBytes(symmetricAlgorithm.BlockSize / 8);
}
private byte[] encrypt(byte[] unencryptedBytes, string password, int keySize)
{
RijndaelManaged aesEncryption = new RijndaelManaged();
aesEncryption.KeySize = keySize;
aesEncryption.BlockSize = 128;
byte[] key = new byte[keySize];
byte[] iv = new byte[128];
getKeyAndIVFromPasswordAndSalt(password, Encoding.ASCII.GetBytes("$391Ge3%£2gfR"), aesEncryption, ref key, ref iv);
aesEncryption.Key = key;
aesEncryption.IV = iv;
Console.WriteLine("iv: {0}", Convert.ToBase64String(aesEncryption.IV));
Console.WriteLine("key: {0}", Convert.ToBase64String(aesEncryption.Key));
ICryptoTransform crypto = aesEncryption.CreateEncryptor();
// The result of the encryption and decryption
return crypto.TransformFinalBlock(unencryptedBytes, 0, unencryptedBytes.Length);
}
The NodeJS code reads like this:
crypto.pbkdf2("Test", "$391Ge3%£2gfR", 1000, 192/8, (err, key) => {
var binkey = new Buffer(key, 'ascii');
var biniv = new Buffer("R6taODpFa1/A7WhTZVszvA==", 'base64');
var decipher = crypto.createDecipheriv('aes192', binkey, biniv);
console.log("KEY: " + binkey.toString("base64"));
var decodedLIL = decipher.update(decryptedBuffer);
console.log(decodedLIL);
return;
});
The IV is hardcoded as I can't figure out how to calculate that using pbkdf2. I've looked through the nodeJS docs for more help but I'm at a loss as to what's going on here.
Any assistance would be greatly appreciated.
One of the issues I see is the encoding of the pound sign (£). crypto.pbkdf2 encodes the password and salt to a binary array by default, where each character is truncated to the lowest 8 bits (meaning the pound sign becomes the byte 0xA3).
However, your C# code converts the salt to ASCII, where each character is truncated to the lowest 7 bits (meaning the pound sign becomes the byte 0x23). Also it uses the Rfc2898DeriveBytes constructor that takes a String for the password. Unfortunately, the documentation doesn't say what encoding is used to convert the string to bytes. Fortunately, Rfc2898DeriveBytes does have another constructor that takes a byte array for the password and also takes an iteration count parameter, here 1000.
Accordingly, you should convert the password and salt strings to byte arrays by truncating each character to 8 bits, just like Node.js does by default. Here is an example:
var bytes=new byte[password.Length];
for(var i=0;i<bytes.Length;i++){
bytes[i]=(byte)(password[i]&0xFF);
}
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 using:
c#: RSACryptoServiceProvider
JAVA: KeyFactory.getInstance("RSA")+Cipher
I sending public key (exponent + modulus) as byte array from java to c#. It's ok, there is the same bytes. But when i try to encrypt some data with one key in Java and c# - there is different results.
Java Key Generation:
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize( Config.CRYPTO_KEY_NUM_BITS );
m_KeyPair = keyGen.genKeyPair();
m_PublicKey = KeyFactory.getInstance("RSA").generatePublic(
newX509EncodedKeySpec(m_KeyPair.getPublic().getEncoded()));
byte[] exponent = m_PublicKey.getPublicExponent().toByteArray();
byte[] modulus = m_PublicKey.getModulus().toByteArray(); // then sending...
C# Key Recieve:
// Recieved...
m_ExternKey = new RSAParameters();
m_ExternKey.Exponent = exponent;
m_ExternKey.Modulus = modulus;
m_RsaExtern = new RSACryptoServiceProvider();
m_RsaExtern.ImportParameters(m_ExternKey);
byte[] test = m_RsaExtern.Encrypt(bytesToEncrypt, true);
and problem is that encrypted bytes is different.
Thank you.
RSA encryption is randomized. For a given public key and a given message, each attempt at encryption yields a distinct sequence of bytes. This is normal and expected; random bytes are injected as part of the padding phase, and not injecting random bytes would result in a weak encryption system. During decryption, the padding bytes are located and removed, and the original message is recovered unscathed.
Hence it is expected that you will get distinct encrypted messages with Java and C#, but also if you run your Java or C# code twice.
RSA Encription mustn't return diffferent values with simular keys - its standardized algorithm. Check your keys.
RSA Parameters contains more parameters than modulus and exponent if i remember correctly. You need fully initialized rsa parameters to get the encryption correct (in .net).
Moreover, your private and private key is not even set in .net
i hope this is helpful , in C# lough code
byte[] rsaExp = rsaParameters.Exponent.ToByteArray();
byte[] Modulus = rsaParameters.Modulus.ToByteArray();
// Microsoft RSAParameters modulo wants leading zero's removed so create new array with leading zero's removed
int Pos = 0;
for (int i = 0; i < Modulus.Length; i++)
{
if (Modulus[i] == 0)
{
Pos++;
}
else
{
break;
}
}
byte[] rsaMod = new byte[Modulus.Length - Pos];
Array.Copy(Modulus, Pos, rsaMod, 0, Modulus.Length - Pos);