How to create RSA key with custom exponent size - c#

I need to create RSA key pair with custom public exponent size (specifically 64 bytes). Creating like this:
var rsa = new RSACng();
PrivateKey = rsa.ExportParameters(true);
or this
var rsa = RSA.Create();
PrivateKey = rsa.ExportParameters(true);
Always results in 3-bytes exponent: 1,0,1.
I've seen people are writing, that RSACng should support custom size exponent, but I can not find any good example.

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.

C# Rfc2898DeriveBytes to node

I'm rewriting an encryption algorithm (implemented in C#) using Node. The encryption uses a 32-byte key and a 16-byte IV and it uses Rfc2898DeriveBytes to generate the key and IV. I have used crypto.pbkdf2Sync to generate a key of 48 bytes instead of 32. The first 32 bits would be the key and the remaining bits would serve as IV as shown below:
const crypto = require( "crypto");
const secret = 'e23d5bb0-2349-289a-d932-abc5a238a873';
const salt = '1bca26a8-49b8-6ad0-b65c-206a96107702';
const algorithm = 'aes-256-xts';
const keyAndIv = crypto.pbkdf2Sync(Buffer.from(secret, "binary"),
Buffer.from(salt, "binary"),
1024,
48,
'sha1');
const key = keyAndIv.slice(0, 32);
const iv = keyAndIv.slice(32, 48);
const cipher = crypto.createCipheriv(algorithm, key, iv);
let encrypted = cipher.update('test', 'utf8', 'hex');
encrypted += cipher.final('hex');
console.log(encrypted);
This is throwing the below error:
Error: Invalid key length
at Cipheriv.createCipherBase (internal/crypto/cipher.js:78:18)
at Cipheriv.createCipherWithIV (internal/crypto/cipher.js:122:20)
at new Cipheriv (internal/crypto/cipher.js:231:22)
at Object.createCipheriv (crypto.js:105:10)
Any thoughts would be really helful.
The cause of the observed behaviour is the used mode, AES-256-XTS.
AES-XTS generates two keys from the passed key and is available as AES-256-XTS and AES-128-XTS. AES-256-XTS expects a 64-byte key and generates from this two 32-byte keys, AES-128-XTS expects a 32-byte key and generates two 16-byte keys.
Instead of padding ciphertext stealing is used. Therefore, plaintext and ciphertext have the same length. In addition, the plaintext must be at least one block (= 16 bytes) in size. More detailed information about AES-XTS can be found here and here.
The use of a 32-byte key in the C#-code speaks for the use of AES-128-XTS and not AES-256-XTS, for which a 64-byte key would be necessary. If the algorithm in the code is changed from AES-256-XTS to AES-128-XTS, the error will no longer occur.
E.g. for the posted input data the plain text
The quick brown fox jumped over the lazy dog
is encrypted with AES-128-XTS in the following ciphertext
b70d9f10ea6c1db513e141290059a73ab7c454e7d0a24fe482c9a6023a783303fe8bcc41bec1734d85af84ba
There's nothing wrong with the key and IV generation.
The NodeJS-method pbkdf2Sync with digest = 'SHA1' is the counterpart to the C#-method Rfc2898DeriveBytes. Both implement PBKDF2 with HMACSHA1 and therefore return the same result for the same input data. E.g. for the posted input data:
secret: e23d5bb0-2349-289a-d932-abc5a238a873
salt: 1bca26a8-49b8-6ad0-b65c-206a96107702
iteration count: 1024
key size: 48
the following byte-sequence is generated:
BE00676F6A3D57EE66FF618FDE5BB15C0E1FC9ECDE5CE949BC784D14ACB7963B49FA9319394A69024A1F359BCC23C703
Below code works for most of the ciphers:
const crypto = require( "crypto");
const base64url = require('base64url');
const secret = 'secret';
const salt = 'add some salt';
const plainText = 'test';
const algorithm = 'aes-256-ctr';
const derivedBytes = crypto.pbkdf2Sync(Buffer.from(secret), Buffer.from(salt), 1024, 48, 'sha1');
const key = derivedBytes.slice(0, 32);
const iv = derivedBytes.slice(32, 48);
const cipher = crypto.createCipheriv(algorithm, key, iv);
const output = Buffer.concat([cipher.update(plainText, 'utf8'), cipher.final()]);
console.log(output.toString('hex'));
console.log(base64url(output));
Detailed blog: https://www.devinstincts.com/2019/05/15/rewrite-c-encryption-using-node-crypto/

Downsides to Using the Same Value for Key and IV?

I am implementing some classes for .NET that (among other things) simplify encryption and decryption.
My current algorithm creates an 8-byte salt, and uses that salt with the password to generate both the key and IV. I then store the salt, unencrypted, with my encrypted data.
This is nice because the salt appears to always be 8 bytes and that's all the overhead it adds to my encrypted data. However, is there any downside to using the same value for both my key and IV? Is there a better way?
Relevant code:
SymmetricAlgorithm algorithm = CreateAlgorithm();
byte[] salt = CreateSalt();
byte[] keyBytes = DeriveBytes(salt, algorithm.KeySize >> 3);
byte[] ivBytes = DeriveBytes(salt, algorithm.BlockSize >> 3);
Supporting code:
private static readonly int SaltLength = 8;
internal byte[] CreateSalt()
{
byte[] salt = new byte[SaltLength];
using (RNGCryptoServiceProvider generator = new RNGCryptoServiceProvider())
{
generator.GetBytes(salt);
}
return salt;
}
public byte[] DeriveBytes(byte[] salt, int bytes)
{
Rfc2898DeriveBytes derivedBytes = new Rfc2898DeriveBytes(Password, salt, 1000);
return derivedBytes.GetBytes(bytes);
}
OK, as long as you use a new, randomly created salt for each message, you are close to what I might do. The random salt means the IV will change with each new message, and this means that the exact same message will be different crypto-text each transmission. All good. The one thing I would change if I were you is instead of using DeriveBytes to get the key and then to get the IV, I would have DeriveBytes give a set of bytes the size of the key and IV together, then split them and use them separately. The IV should not have to be secret from anyone. The key must be. So if you DeriveBytes once from the same salt and password, then split those bytes into key and IV, the attacker is still no closer to knowing the key after looking at the IV than he was before.
Alternatively, you could use a nonce to create a known permutation between the IV bytes and the key bytes. For example, excuse my pseudocode:
IV = DeriveBytes(salt + password + "IV")
key = DeriveBytes(salt + password + "key")
Either way is secure. But I would just DeriveBytes on, say, 32 bytes and then use 16 of them for the IV and 16 of them for the key. There is no information in the first 16 bytes that will help an attacker calculate the next 16 bytes.
Yes, it defeats the purpose of the IV. The IV is used so if you encrypt the same message with the same key you don't get the same ciphertext. You might as well just use a constant value of 0, it adds the same amount of security.
In your case here you are using the same value for your
Key
IV
Conceptually this is a bad idea because the IV is supposed to be non-secret, and different for each encryption. You've solved the "different for each encryption", but you have it identical to your key.
The thing you're trying to defend against is making sure that two encryptions with the same key will not give the same ciphertext. In your case, this will only happen if the RNG generates two identical 128-bit AES keys.
While the odds of this are low, you should just not have it.

Creating an RSA PKCS1 v1.5 key

I'm trying to create an RSA PKCS v1.5 key from a modulus and exponent, this is how far I got:
BigInteger mod = BigInteger.Parse(loginData["publickey_mod"], System.Globalization.NumberStyles.HexNumber);
BigInteger exp = BigInteger.Parse(loginData["publickey_exp"], System.Globalization.NumberStyles.HexNumber);
string timestamp = loginData["timestamp"];
string steamid = loginData["steamid"];
RSAParameters loginRSA = new RSAParameters();
loginRSA.Modulus = mod.ToByteArray();
loginRSA.Exponent = exp.ToByteArray();
However after searching the web for quite some times (>1 hour) I couldn't find anyway to create an RSA key... I need to create one to encode a password. Is there anyone that could help me a little further?
Once you have the RSAParameters of the public key information then just import it directly into an RSA instance:
using (var rsa = new RSACryptoServiceProvider())
{
// Import public key
rsa.ImportParameters(loginRSA);
// Encrypt some data
var cipherText = rsa.EncryptValue(someData);
}

RSA algorithm with use of RSACryptoServiceProvider

I have a little hard time understanding the RSACryptoServiceProvider class... I'm supposed to encrypt a message of length 256 bits, with a key , which is also 256 bits long. Shouldn't the output of also be 256 bits long?
Here's my code:
//key generation
byte[] bytes = new byte[32];
var rng = new RNGCryptoServiceProvider();
rng.GetBytes(bytes);
k2 = bytes;
//encryption function
static public byte[] Encryption(byte[] Data, RSAParameters RSAKey, bool DoOAEPPadding)
{
byte[] encryptedData;
using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
{
RSA.ImportParameters(RSAKey);
encryptedData = RSA.Encrypt(Data, DoOAEPPadding);
}
return encryptedData;
}
And then finally calculating
ciphertext = Encryption(k2, RSA.ExportParameters(false), false);
produces a byte[128] ciphertext aka 1024 bits. Shouldn't I get ciphertext of size byte[32]?
It seems that you use the key, k2, as data for RSA encryption. That's OK if you want e.g. to wrap a 256 bit AES key using RSA. But your RSA key is the second parameter, not the first.
The data in k2 is then padded (according to the older PKCS#1 v1.5 scheme), after which modular exponentiation will be performed using the public exponent and modulus of the RSA key. The modulus of the RSA key determines the key size. This modulus exponentiation will always produce a result between zero and modulus - 1. However, the result is always left-padded to the key size in bytes (with a function called I2OSP).
So it seems your result is 1024 bits, which means that your RSA key pair is also 1024 bits.

Categories

Resources