How to convert JSON Web Key to PEM format in Delphi? - encryption

Earlier I found out that before using the signature in JWT Library, I need to convert the JSON Web Key (JWK) to PEM format.
Original private key in JWK format:
{
"kty": "EC",
"d": "Rwyv99W3GnfjYbI0X-b5Umhvh88oRCKQkPxiwCPVGgg",
"crv": "P-256",
"x": "sDbcYT8HzBk1tUl849ZHrhpIn8ZV7HfD1DwYdsP1ip0",
"y": "EWodfKWQ6oE0ppyi7tRO_61BgAQsZyDjDGj9kLZiUts"
}
Need to get PEM format, like here:
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIEcMr/fVtxp342GyNF/m+VJob4fPKEQikJD8YsAj1RoIoAoGCCqGSM49
AwEHoUQDQgAEsDbcYT8HzBk1tUl849ZHrhpIn8ZV7HfD1DwYdsP1ip0Rah18pZDq
gTSmnKLu1E7/rUGABCxnIOMMaP2QtmJS2w==
-----END EC PRIVATE KEY-----
There is an online converter that does what I need. Is it possible to do the same convertation in Delphi?

You would have everything you need in low-level OpenSSL.
Its API is a little cryptic but you have the EC_POINT*() functions for doing it.
Check what we did in
mormot.crypt.openssl to work with low-level ECC private keys and integrate them with OpenSSL:
ecdsa_sign_osl which takes a raw private key and convert it into OpenSSL PEC_KEY;
OpenSslSaveKeys which saves this key as PEM.
You need to only export the "d": "Rwyv99W3GnfjYbI0X-b5Umhvh88oRCKQkPxiwCPVGgg" parameter. It seems to be the same layout than TEccPrivateKey as used as input parameter in ecdsa_sign_osl().
You may find also some pure pascal code computing ECC prime256v1 in mormot.crypt.ecc256r1.pas.

The solution was found. Read the details here.
Simplified example:
uses
JSON,
EncdDecd;
function Base64urlToBase64(Base64urlStr: String): String;
begin
Result := StringReplace(Base64urlStr,'_','/', [rfReplaceAll]);
Result := StringReplace(Result,'-','+', [rfReplaceAll]);
end;
function JwkToPem(JWK: TJSONObject): String;
var
BinKey: TBytes;
begin
BinKey :=
[$30] + // ASN.1
[$77] + // Length of all following bytes (119 bytes)
[$02] + // Type (integer)
[$01] + // Length of integer (1 byte)
[$01] + // Value of integer (1)
[$04] + // Type (octet string)
[$20] + // Length of string (32 bytes)
DecodeBase64(Base64urlToBase64(JWK.Get('d').JsonValue.Value)) + // Private Key
[$A0] + // Tag 0
[$0A] + // Length of tag (10 bytes)
[$06] + // Type (Object ID)
[$08] + // Length of the Object ID (8 bytes)
[$2A, $86, $48, $CE, $3D, $03, $01, $07] + // - The object ID of the curve prime256v1
[$A1] + // Tag 1
[$44] + // Length of tag (68 bytes)
[$03] + // Type – Bit string
[$42] + // Length of the bit string (66 bytes)
[$00] + // ???
[$04] + // Uncompressed Public Key
DecodeBase64(Base64urlToBase64(JWK.Get('x').JsonValue.Value))+ // Public Key X coord
DecodeBase64(Base64urlToBase64(JWK.Get('y').JsonValue.Value)); // Public Key Y coord
Result :=
'-----BEGIN EC PRIVATE KEY-----'+#13#10+
EncodeBase64(Pointer(BinKey), Length(BinKey))+#13#10+
'-----END EC PRIVATE KEY-----';
end;

Related

How to decrypt webhook response data in Microsoft Graph?

I have implemented Secure Web hook features for my Spring Boot application(Java).
For that I have created "Subscription" with below JSON.
String subscriptionMessageTemplate = "{\"changeType\": \"created,updated\",\"notificationUrl\": \"%s/notify/messages\",\"lifecycleNotificationUrl\":\"%s/notify/messages/lifeCycle\", \"resource\": \"/teams/{id}/channels/19:{id}#thread.skype/messages\", \"clientState\": \"secretClientValue\",\"includeResourceData\": true,\"encryptionCertificate\": \"%s\",\"expirationDateTime\":\"%s\",\"encryptionCertificateId\": \"1\"}";
I have used ngrok for public IP:
When I am sending message from the team, I am getting below response.
{
"value": [
{
"subscriptionId": "76222963-cc7b-42d2-882d-8aaa69cb2ba3",
"changeType": "created",
// Other properties typical in a resource change notification
"resource": "teams('d29828b8-c04d-4e2a-b2f6-07da6982f0f0')/channels('19:f127a8c55ad949d1a238464d22f0f99e#thread.skype')/messages('1565045424600')/replies('1565047490246')",
"resourceData": {
"id": "1565293727947",
"#odata.type": "#Microsoft.Graph.ChatMessage",
"#odata.id": "teams('88cbc8fc-164b-44f0-b6a6-b59b4a1559d3')/channels('19:8d9da062ec7647d4bb1976126e788b47#thread.tacv2')/messages('1565293727947')/replies('1565293727947')"
},
"encryptedContent": {
"data": "{encrypted data that produces a full resource}",
"dataSignature": "<HMAC-SHA256 hash>",
"dataKey": "{encrypted symmetric key from Microsoft Graph}",
"encryptionCertificateId": "MySelfSignedCert/DDC9651A-D7BC-4D74-86BC-A8923584B0AB",
"encryptionCertificateThumbprint": "07293748CC064953A3052FB978C735FB89E61C3D"
}
}
],
"validationTokens": [
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSU..."
]
}
Now I want to decrypt data, Can any one help me to how to decrypt data in Java?
For certificate generation, I have used my custom method:
strong text.
private void generateSelfSignedX509Certificate(KeyPair keyPair) throws Exception {
// yesterday
Date validityBeginDate = new Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000);
// in 2 years
Date validityEndDate = new Date(System.currentTimeMillis() + 2 * 365 * 24 * 60 * 60 * 1000);
// GENERATE THE X509 CERTIFICATE
X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
X500Principal dnName = new X500Principal("CN=John Doe");
certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
certGen.setSubjectDN(dnName);
certGen.setIssuerDN(dnName); // use the same
certGen.setNotBefore(validityBeginDate);
certGen.setNotAfter(validityEndDate);
certGen.setPublicKey(keyPair.getPublic());
certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
this.certificate = certGen.generate(keyPair.getPrivate(), "BC");
}
I had to struggle my way with it in Python 3.6, so for the sake of future python-readers, here is my skeleton of working code that do the above (using pycryptodome==3.9.7):
import json
import hashlib, hmac
from base64 import b64decode, b64encode
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Util.Padding import unpad
from Crypto.PublicKey import RSA
from Crypto.Cipher import AES
...
...
encrypted_symmetric_key: bytes = b64decode(encrypted_symmetric_key.encode())
encrypted_payload = b64decode(encrypted_payload.encode())
rsa_key = RSA.import_key(private_key, passphrase=private_key_passphrase)
cipher = PKCS1_OAEP.new(rsa_key)
# if length of encrypted_symmetric_key is > 128 we will get ciphertext with incorrect length, to avoid that lets split and decrypt in chunks
default_length = 128
length = len(encrypted_symmetric_key)
if length < default_length:
decrypt_byte = cipher.decrypt(encrypted_symmetric_key)
else:
offset = 0
res = []
while length - offset > 0:
if length - offset > default_length:
res.append(cipher.decrypt(encrypted_symmetric_key[offset:offset + default_length]))
else:
res.append(cipher.decrypt(encrypted_symmetric_key[offset:]))
offset += default_length
decrypt_byte = b''.join(res)
decrypted_symmetric_key = decrypt_byte
hash_state_machine = hmac.new(decrypted_symmetric_key, msg=encrypted_payload, digestmod=hashlib.sha256)
raw_signature = hash_state_machine.digest()
actual_signature_bytes: bytes = b64encode(raw_signature)
actual_signature: str = actual_signature_bytes.decode()
if actual_signature != expected_data_signature:
raise Exception("data hash is not as expected")
iv = decrypted_symmetric_key[:16]
cipher2 = AES.new(decrypted_symmetric_key, AES.MODE_CBC, iv=iv)
message_str = unpad(cipher2.decrypt(encrypted_payload), block_size=16).decode()
message_dict = json.loads(message_str)
I updated the documentation to include Java samples. I'll also include the samples here for reference but future readers should refer to the documentation which is where the samples will be kept up to date.
Keep in mind these samples operate under the assumption that you have a local Java Key Store (JKS) that the certificate is pulled from.
Decrypt the AES key:
String storename = ""; //name/path of the jks store
String storepass = ""; //password used to open the jks store
String alias = ""; //alias of the certificate when store in the jks store, should be passed as encryptionCertificateId when subscribing and retrieved from the notification
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(storename), storepass.toCharArray());
Key asymmetricKey = ks.getKey(alias, storepass.toCharArray());
byte[] encryptedSymetricKey = Base64.decodeBase64("<value from dataKey property>");
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, asymmetricKey);
byte[] decryptedSymmetricKey = cipher.doFinal(encryptedSymetricKey);
Verify the data signature
byte[] decryptedSymmetricKey = "<the aes key decrypted in the previous step>";
byte[] decodedEncryptedData = Base64.decodeBase64("data property from encryptedContent object");
Mac mac = Mac.getInstance("HMACSHA256");
SecretKey skey = new SecretKeySpec(decryptedSymmetricKey, "HMACSHA256");
mac.init(skey);
byte[] hashedData = mac.doFinal(decodedEncryptedData);
String encodedHashedData = new String(Base64.encodeBase64(hashedData));
if (comparisonSignature.equals(encodedHashedData);)
{
// Continue with decryption of the encryptedPayload.
}
else
{
// Do not attempt to decrypt encryptedPayload. Assume notification payload has been tampered with and investigate.
}
Decrypt the data content
SecretKey skey = new SecretKeySpec(decryptedSymmetricKey, "AES");
IvParameterSpec ivspec = new IvParameterSpec(Arrays.copyOf(decryptedSymmetricKey, 16));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skey, ivspec);
String decryptedResourceData = new String(cipher.doFinal(Base64.decodeBase64(encryptedData)));
optimized the previous answer a bit excluding the hash validations
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import serialization
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from config.config import Config
import base64
import json
config = Config('config.yaml')
notification_response = json.loads(msg)
base64encodedKey = notification_response['value'][0]['encryptedContent']['dataKey']
asymetricPrivateKey = bytes(config.subscription.PRIVATE_KEY,encoding='utf-8')
decodedKey = base64.b64decode(base64encodedKey)
private_key = serialization.load_pem_private_key(
asymetricPrivateKey,
password=None,
backend=default_backend()
)
decryptedSymetricKey = private_key.decrypt(
decodedKey,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA1()),
algorithm=hashes.SHA1(),
label=None
)
)
encrypted_payload = base64.b64decode(notification_response['value'][0]['encryptedContent']['data'].encode())
iv = decryptedSymetricKey[:16]
cipher2 = AES.new(decryptedSymetricKey, AES.MODE_CBC, iv=iv)
message_str = unpad(cipher2.decrypt(encrypted_payload), block_size=16).decode()
message_dict = json.loads(message_str)

decrypt AES input on Flutter, when on web use cryptoJS AES

On web, I'm using CryptoJS for decrypto JS:
CryptoJS.AES.decrypt(inputBase64, key).toString(CryptoJS.enc.Utf8);
Example:
input: "tzfwnxVwE/qNoaWRRfqLp11ZyhB4UtKO+0/Lvv5B7eE="
key: "20190225165436_15230006321670000_15510884759030000"
On flutter I can't find any library to decrypt with a key of any length.
I know "For AES, NIST selected three members of the Rijndael family, each with a block size of 128 bits, but three different key lengths: 128, 192 and 256 bits.
"
But I don't know how to convert any length key to 128 bit format?
import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
import 'package:tuple/tuple.dart';
import 'package:encrypt/encrypt.dart' as encrypt;
String encryptAESCryptoJS(String plainText, String passphrase) {
try {
final salt = genRandomWithNonZero(8);
var keyndIV = deriveKeyAndIV(passphrase, salt);
final key = encrypt.Key(keyndIV.item1);
final iv = encrypt.IV(keyndIV.item2);
final encrypter = encrypt.Encrypter(
encrypt.AES(key, mode: encrypt.AESMode.cbc, padding: "PKCS7"));
final encrypted = encrypter.encrypt(plainText, iv: iv);
Uint8List encryptedBytesWithSalt = Uint8List.fromList(
createUint8ListFromString("Salted__") + salt + encrypted.bytes);
return base64.encode(encryptedBytesWithSalt);
} catch (error) {
throw error;
}
}
String decryptAESCryptoJS(String encrypted, String passphrase) {
try {
Uint8List encryptedBytesWithSalt = base64.decode(encrypted);
Uint8List encryptedBytes =
encryptedBytesWithSalt.sublist(16, encryptedBytesWithSalt.length);
final salt = encryptedBytesWithSalt.sublist(8, 16);
var keyndIV = deriveKeyAndIV(passphrase, salt);
final key = encrypt.Key(keyndIV.item1);
final iv = encrypt.IV(keyndIV.item2);
final encrypter = encrypt.Encrypter(
encrypt.AES(key, mode: encrypt.AESMode.cbc, padding: "PKCS7"));
final decrypted =
encrypter.decrypt64(base64.encode(encryptedBytes), iv: iv);
return decrypted;
} catch (error) {
throw error;
}
}
Tuple2<Uint8List, Uint8List> deriveKeyAndIV(String passphrase, Uint8List salt) {
var password = createUint8ListFromString(passphrase);
Uint8List concatenatedHashes = Uint8List(0);
Uint8List currentHash = Uint8List(0);
bool enoughBytesForKey = false;
Uint8List preHash = Uint8List(0);
while (!enoughBytesForKey) {
int preHashLength = currentHash.length + password.length + salt.length;
if (currentHash.length > 0)
preHash = Uint8List.fromList(
currentHash + password + salt);
else
preHash = Uint8List.fromList(
password + salt);
currentHash = md5.convert(preHash).bytes;
concatenatedHashes = Uint8List.fromList(concatenatedHashes + currentHash);
if (concatenatedHashes.length >= 48) enoughBytesForKey = true;
}
var keyBtyes = concatenatedHashes.sublist(0, 32);
var ivBtyes = concatenatedHashes.sublist(32, 48);
return new Tuple2(keyBtyes, ivBtyes);
}
Uint8List createUint8ListFromString(String s) {
var ret = new Uint8List(s.length);
for (var i = 0; i < s.length; i++) {
ret[i] = s.codeUnitAt(i);
}
return ret;
}
Uint8List genRandomWithNonZero(int seedLength) {
final random = Random.secure();
const int randomMax = 245;
final Uint8List uint8list = Uint8List(seedLength);
for (int i=0; i < seedLength; i++) {
uint8list[i] = random.nextInt(randomMax)+1;
}
return uint8list;
}
Usage
import 'package:app/utils/cryptojs_aes_encryption_helper.dart';
String plainText = 'PlainText is Me';
var encrypted = encryptAESCryptoJS(plainText, "password");
var decrypted = decryptAESCryptoJS(encrypted, "password");
When you pass CryptoJS a string as the key it treats it as a passphrase and generates the key from it using a key derivation function - in this case PBKDF2. It generates a 256 bit key and a 128 bit initialization vector (IV). It then uses those for the encryption/decryption. You also need to find out what chaining method CryptoJS uses (probably cipher block chaining (CBC)) and what padding method it uses (to make sure that the plain text is a round number of 128 bit blocks - probably PKCS#7).
CryptoJS has this "works out of the box" mode, but it isn't particularly clear what it's doing under the hood - you'd need to read the source code or scour the documentation.
When trying to inter-operate between two systems/languages it's best if you remain in charge of things, rather than letting one end make arbitrary decisions. That way you can make sure that you have the settings the same at each end.
So, you might choose to:
Use PBKDF2 to generate a 128 bit key and 128 bit IV from the string
passphrase - with, say, 9999 rounds
Use PKCS#7 padding
Use AES in CBC mode
The pointycastle package supports all the above in Dart. It looks like CryptoJS supports all of those too.
Start with a passphrase and make sure you can generate the same key and IV in JS and Dart. Then move onto creating the ciphers.
Remember, too, never to encrypt two messages with the same key/IV pair. Use a message sequence number, for example, to slightly change the IV for each message.
Sample encrypt using nodejs script:
var key = CryptoJS.PBKDF2("123456", "123456", {
keySize: 256 / 32
});
var iv = CryptoJS.PBKDF2("123456", "123456", {
keySize: 128 / 32
});
var encrypted = CryptoJS.AES.encrypt('my message', key, { iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}).toString();
decript using dart
import 'package:encrypt/encrypt.dart' as aes;
import 'package:crypto/crypto.dart';
import 'package:hex/hex.dart';
import 'package:password_hash/pbkdf2.dart';
void main(List<String> arguments) {
String encrypted = 'HbsmGAigiIWmU3MNZAf8+w==';
final generator = PBKDF2(hashAlgorithm: sha1);
final key = aes.Key.fromBase16(HEX.encode(generator.generateKey("123456", "123456", 1, 32)));
final iv = aes.IV.fromBase16(HEX.encode(generator.generateKey("123456", "123456", 1, 16)));
final encrypter = aes.Encrypter(aes.AES(key, mode: aes.AESMode.cbc, padding: 'PKCS7'));
final decrypted = encrypter.decrypt64(encrypted, iv:iv);
print(decrypted);
}

encrypt using ase256 gives different output in python and nodejs

I am trying to encrypt a string "1" using key = "secret_key" and text "11869021012". Earlier I had written this in nodejs. now I want to port this to python. but here surprisingly both are giving different outputs.
var crypto = require('crypto');
function getBytes (str) {
let bytes = [], char;
str = encodeURI(str);
while (str.length) {
char = str.slice(0, 1);
str = str.slice(1);
if ('%' !== char) {
bytes.push(char.charCodeAt(0));
} else {
char = str.slice(0, 2);
str = str.slice(2);
bytes.push(parseInt(char, 16));
}
}
return bytes;
};
function getIV (str, bytes){
iv = getBytes(str);
if(!bytes) bytes = 16;
for(let i=iv.length;i<bytes;i++) {
iv.push(0);
}
return Buffer.from(iv);
};
function getKey (pwd){
pwd = Buffer.from(getBytes(pwd), 'utf-8');
let hash = crypto.createHash('sha256');
pwd = hash.update(pwd).digest();
return pwd;
};
function createCipherIV (algorithm, input_key, iv_input, text){
let iv = getIV(iv_input);
let key = getKey(input_key);
let cipher = crypto.createCipheriv(algorithm, key, iv);
let encrypted = cipher.update(text)
encrypted += cipher.final('base64');
return encrypted;
}
output = createCipherIV('aes256', 'secret_key', '11869021012', '1')
console.log(output)
This produces the output:
s6LMaE/YRT6y8vr2SehLKw==
python code:
# AES 256 encryption/decryption using pycrypto library
import base64
import hashlib
from Crypto.Cipher import AES
from Crypto import Random
BLOCK_SIZE = 16
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * chr(BLOCK_SIZE - len(s) % BLOCK_SIZE)
unpad = lambda s: s[:-ord(s[len(s) - 1:])]
password = "secret_key"
def encrypt(raw, password):
private_key = hashlib.sha256(bytearray(password, "utf-8")).digest()
raw = pad(raw)
iv = b'11869021012\x00\x00\x00\x00\x00'
cleartext = bytearray(raw, 'utf-8')
cipher = AES.new(private_key, AES.MODE_CBC, iv)
return base64.b64encode(iv + cipher.encrypt(cleartext))
# First let us encrypt secret message
encrypted = encrypt("1", password)
print(encrypted)
This produces the output:
MTE4NjkwMjEwMTIAAAAAALOizGhP2EU+svL69knoSys=
I have used aes256 algorithm here for encrypting message.
Clearly they are very close, but node seems to be padding the output with some extra bytes. Any ideas how I can get the two to interoperate?
First, in a secure crypto system, you should expect the output to be different every time you encrypt, even using the same code. That fact that yours doesn't indicates it's an insecure cipher. Typically this is done by adding a random IV.
Your IV is "11869021012", which is horrible (because it's not random, and not even 16 bytes), but it does seem you're using it the same way in both, so that's fine.
Your password is the SHA-256 of a string, which is a horrible way to create a key, but still, you seem to be doing it the same way in both cases, so that's fine.
Your problem is that the Python code emits the IV followed by the cipher text. Your JS code does not emit the IV; it only emits the cipher text. So you probably meant this in the Python:
return base64.b64encode(cipher.encrypt(cleartext))
Or you need to rework the JavaScript to glue together the IV and the cipher text before Base64 encoding.

Java Signature Timings/Overhead

I'm trying to make use of Java signatures in a pretty time sensitive setting.
I've come across some interesting behavior when signing data of a few hundred bytes. I am reusing a generated key, but recreating the Signature object each time I sign. The first time the signing happens it takes from anywhere from 50-100ms depending on the machine being used. However, any subsequent times a signature is computed over new data (using the same key) the time is reduced down to 1-2ms. I'm using SHA512 with RSA so I expected it to be heavier.
Can anyone explain why this happens? A test class I am using is pasted below (which is using identical code to my target applictaion).
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
public class MainClass {
public static void main(String args[]) throws Exception {
//Generate key pair
long start = System.currentTimeMillis();
KeyPair keyPair = generateKeyPair(999);
long end = System.currentTimeMillis();
System.out.println("KeyGen: " + (end - start));
//Sign first piece of data
byte[] data = { ** a few hundred bytes** };
start = System.currentTimeMillis();
byte[] digitalSignature = signData(data, keyPair.getPrivate());
end = System.currentTimeMillis();
System.out.println("Sign: " + (end - start));
boolean verified;
byte[] data2 = {** a different few hundred bytes ** };
//sign second piece of data
start = System.currentTimeMillis();
digitalSignature = signData(data2, keyPair.getPrivate());
end = System.currentTimeMillis();
System.out.println(" Second Sign: " + (end - start));
//verify second signature
start = System.currentTimeMillis();
verified = verifySig(data2, keyPair.getPublic(), digitalSignature);
end = System.currentTimeMillis();
System.out.println("Verify: " + (end - start));
System.out.println(verified);
}
public static byte[] signData(byte[] data, PrivateKey key) throws Exception {
Signature signer = Signature.getInstance("SHA512withRSA");
signer.initSign(key);
signer.update(data);
return (signer.sign());
}
public static boolean verifySig(byte[] data, PublicKey key, byte[] sig) throws Exception {
Signature signer = Signature.getInstance("SHA512withRSA");
signer.initVerify(key);
signer.update(data);
return (signer.verify(sig));
}
public static KeyPair generateKeyPair(long seed) throws Exception {
KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("RSA");
SecureRandom rng = SecureRandom.getInstance("SHA1PRNG", "SUN");
rng.setSeed(seed);
keyGenerator.initialize(1024, rng);
return (keyGenerator.generateKeyPair());
}
}
This results in the output (time in ms)
KeyGen: 81
Sign: 52
Second Sign: 2
Verify: 2
I put some benchmarks on each line of the sign method, and it appears the overhead is purely from the sign action itself.
Create Sig Obj: 1
Init sign: 1
update: 0
Sign: 49
Create Sig Obj: 1
Init sign: 0
update: 0
Second Sign: 2
Any insight would be much appreciated

Encryption Failing with error Invalid length for a Base-64 char array or string

I am trying to encrypt a PRIVATE KEY with a passphrase so I can save the file to disk. But the encryption method is throwing the exception: "Invalid length for a Base-64 char array or string".
The Encryption method is (it's a 2048bit key length):
public static string Encrypt(string plainString, string key, int keySize = Keysize.SymmetricKeyLength)
{
var aesEncryption = new RijndaelManaged
{
KeySize = keySize,
BlockSize = 128,
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7,
IV = Convert.FromBase64String(Encoding.UTF8.GetString(Convert.FromBase64String(key)).Split(',')[0]),
Key = Convert.FromBase64String(Encoding.UTF8.GetString(Convert.FromBase64String(key)).Split(',')[1])
};
byte[] plainText = Encoding.UTF8.GetBytes(plainString);
ICryptoTransform crypto = aesEncryption.CreateEncryptor();
// The result of the encryption and decryption
byte[] cipherText = crypto.TransformFinalBlock(plainText, 0, plainText.Length);
return Convert.ToBase64String(cipherText);
}
I am converting the Private Key to a Base64 string before passing it down to the encryption method using this method:
public string EncodeTo64(string plainString)
{
var bytes = Encoding.UTF8.GetBytes(plainString);
return Convert.ToBase64String(bytes);
}
The Private Key is:
<RSAKeyValue><Modulus>rhtMjLTg17sYlns4ktTow9eeuwRNra0+AO2HqESGmA8zkxe/uOv0msXzzLWUWzdPaTxi4OV+PNVPBAHW1C0CTT/33NlvipkJ1Qr5BJK1TiVZCMInshe4OL/7GNnPUPhsS6DZ/c/fnWLoxtRMUmkKgpWmtXGs7ZSoIztdJ1bgiygJWCDvrHTokVIzDaNzRonZIFk41Qt4rPofCEawjkR639OcOfazNlmU9JjvRs3ysoYghDzvVuLvJvPK7zCMzpJMQFQE7cipezXbumTqSdp20mQXJduDbD9qLKXOvcTw+2KPoNlUp+IRQrOmSf+Dl4Vxi+8+UuOH7KDLz7yL9IOPeQ==</Modulus><Exponent>AQAB</Exponent><P>2AHey3Tgg/K8M16kv6bWk1BsFGhg9xXZw2ruVhS620gyvPBtWBuOU+tzPRnEplw+Kp9jua7Nu4JkKwpQdZvRqeW42d/UCergkdNRheM3DXYj/xQNs8a1diTNe72elCsCfSHr1z/vgN+Cp+v8O4BzX07TrHeGOOP/7HWhE6setxM=</P><Q>zld05TyC/vVI2sBgaR/iYyXdUO3iIIwkGSyOmfDr1dbCKFR7btGLEsW9EpCGibyGPbAk4jA9BLU1bviBM8iH6mxWn1s4UAiIha0QSM2K9NWUPi67FELl6Fs2eLHl9qRniBhAOBCGArklail+YadKCtUsrWhfJgvO3uxkp+fg9MM=</Q><DP>sziaCmVnAxObY2PbfciHsKLBig0wptHSZHmMVo/MmbRFpM43aysx5B8u9jszFnTif6rPq3iF6lY9lhhwuaQXScf4n40++RuQSG307gmf2+Nx6mpRFCCC3wuaElk6AeXNotVKQMYjieHpHjqGhTgGgcV9i1OAYiOKbD8M7qzER1E=</DP><DQ>FEazzfLsTHF9/0D4OFxRurx1ywYVOm2K/o5KVQY/pnu8CIqEtpcQu3+C3Ngm4FIOPvGYLkHfPR8xaP4ydAw4juimenJUTkkIYVpoRz8rcHOsZY/iAlOwk+yipamVl28AXXdEmD3HbW0UKCJ7sMznkbjw8vlWoD54zZ8dJQK8MFE=</DQ><InverseQ>FUFC9v5B1mXxbbiD4WZm/KGIa3XO5+K9FwSRroj8wNMt+JY5aMS8SfUcrZMvUXfHS9+3BYXBIlxPBUm6HnfB7yPE9S0LFzRpB7APbJ0HVIlSjMS9ZdkqxShGAEufYx/FKQXomJlEXXkpgAiDnUnCR2H+ekQf1YpQUzol2KedwfA=</InverseQ><D>gEhc/s/HWyzf0QC5jnaRirs0mVdyZKVhKg3aBoF3KlMJDThSa05vzBpOqGaiCROXz1JPCKYPfYMt1SYFxA/lwkV/u5n6NYTNWcvb7yKptAqQr4Ne/Dm94xKRUJ4rwt1H7fF2rSyc9roKCXYjRhVfSRg63TYE1IjT2iHDYVkB2YVPK67O2O7YmQXeUHMRMVwXpnZCvweleRKlYbVFx2N7ZEC1TZoUn2RKsiBEem1eNSwnLa4wUf1Xl8Q8h+ziY0GnREf9JpTZhJW7f4MKsqLyOMgmoskKiIOWlnwq/b01ivB2CXFhxiiVuNUPPiMuJu6bhljeulvKl32kEzLAFxm2gQ==</D></RSAKeyValue>
and the resulting Base64 string from the above conversion method is:
PFJTQUtleVZhbHVlPjxNb2R1bHVzPnJodE1qTFRnMTdzWWxuczRrdFRvdzllZXV3Uk5yYTArQU8ySHFFU0dtQTh6a3hlL3VPdjBtc1h6ekxXVVd6ZFBhVHhpNE9WK1BOVlBCQUhXMUMwQ1RULzMzTmx2aXBrSjFRcjVCSksxVGlWWkNNSW5zaGU0T0wvN0dOblBVUGhzUzZEWi9jL2ZuV0xveHRSTVVta0tncFdtdFhHczdaU29JenRkSjFiZ2l5Z0pXQ0R2ckhUb2tWSXpEYU56Um9uWklGazQxUXQ0clBvZkNFYXdqa1I2MzlPY09mYXpObG1VOUpqdlJzM3lzb1lnaER6dlZ1THZKdlBLN3pDTXpwSk1RRlFFN2NpcGV6WGJ1bVRxU2RwMjBtUVhKZHVEYkQ5cUxLWE92Y1R3KzJLUG9ObFVwK0lSUXJPbVNmK0RsNFZ4aSs4K1V1T0g3S0RMejd5TDlJT1BlUT09PC9Nb2R1bHVzPjxFeHBvbmVudD5BUUFCPC9FeHBvbmVudD48UD4yQUhleTNUZ2cvSzhNMTZrdjZiV2sxQnNGR2hnOXhYWncycnVWaFM2MjBneXZQQnRXQnVPVSt0elBSbkVwbHcrS3A5anVhN051NEprS3dwUWRadlJxZVc0MmQvVUNlcmdrZE5SaGVNM0RYWWoveFFOczhhMWRpVE5lNzJlbENzQ2ZTSHIxei92Z04rQ3ArdjhPNEJ6WDA3VHJIZUdPT1AvN0hXaEU2c2V0eE09PC9QPjxRPnpsZDA1VHlDL3ZWSTJzQmdhUi9pWXlYZFVPM2lJSXdrR1N5T21mRHIxZGJDS0ZSN2J0R0xFc1c5RXBDR2lieUdQYkFrNGpBOUJMVTFidmlCTThpSDZteFduMXM0VUFpSWhhMFFTTTJLOU5XVVBpNjdGRUxsNkZzMmVMSGw5cVJuaUJoQU9CQ0dBcmtsYWlsK1lhZEtDdFVzcldoZkpndk8zdXhrcCtmZzlNTT08L1E+PERQPnN6aWFDbVZuQXhPYlkyUGJmY2lIc0tMQmlnMHdwdEhTWkhtTVZvL01tYlJGcE00M2F5c3g1Qjh1OWpzekZuVGlmNnJQcTNpRjZsWTlsaGh3dWFRWFNjZjRuNDArK1J1UVNHMzA3Z21mMitOeDZtcFJGQ0NDM3d1YUVsazZBZVhOb3RWS1FNWWppZUhwSGpxR2hUZ0dnY1Y5aTFPQVlpT0tiRDhNN3F6RVIxRT08L0RQPjxEUT5GRWF6emZMc1RIRjkvMEQ0T0Z4UnVyeDF5d1lWT20ySy9vNUtWUVkvcG51OENJcUV0cGNRdTMrQzNOZ200RklPUHZHWUxrSGZQUjh4YVA0eWRBdzRqdWltZW5KVVRra0lZVnBvUno4cmNIT3NaWS9pQWxPd2sreWlwYW1WbDI4QVhYZEVtRDNIYlcwVUtDSjdzTXpua2Jqdzh2bFdvRDU0elo4ZEpRSzhNRkU9PC9EUT48SW52ZXJzZVE+RlVGQzl2NUIxbVh4YmJpRDRXWm0vS0dJYTNYTzUrSzlGd1NScm9qOHdOTXQrSlk1YU1TOFNmVWNyWk12VVhmSFM5KzNCWVhCSWx4UEJVbTZIbmZCN3lQRTlTMExGelJwQjdBUGJKMEhWSWxTak1TOVpka3F4U2hHQUV1Zll4L0ZLUVhvbUpsRVhYa3BnQWlEblVuQ1IySCtla1FmMVlwUVV6b2wyS2Vkd2ZBPTwvSW52ZXJzZVE+PEQ+Z0VoYy9zL0hXeXpmMFFDNWpuYVJpcnMwbVZkeVpLVmhLZzNhQm9GM0tsTUpEVGhTYTA1dnpCcE9xR2FpQ1JPWHoxSlBDS1lQZllNdDFTWUZ4QS9sd2tWL3U1bjZOWVROV2N2Yjd5S3B0QXFRcjROZS9EbTk0eEtSVUo0cnd0MUg3ZkYyclN5Yzlyb0tDWFlqUmhWZlNSZzYzVFlFMUlqVDJpSERZVmtCMllWUEs2N08yTzdZbVFYZVVITVJNVndYcG5aQ3Z3ZWxlUktsWWJWRngyTjdaRUMxVFpvVW4yUktzaUJFZW0xZU5Td25MYTR3VWYxWGw4UThoK3ppWTBHblJFZjlKcFRaaEpXN2Y0TUtzcUx5T01nbW9za0tpSU9XbG53cS9iMDFpdkIyQ1hGaHhpaVZ1TlVQUGlNdUp1NmJobGpldWx2S2wzMmtFekxBRnhtMmdRPT08L0Q+PC9SU0FLZXlWYWx1ZT4=
Apparently this Base64 string is invalid and the encryption fails.
Can anybody see where I am going wrong?
Your Base64 string appears to be valid. When I decode it I get some recognisable XML for RSA:
<RSAKeyValue>
<Modulus>rht ... PeQ==</Modulus>
<Exponent>AQAB</Exponent>
<P>2AH ... txM=</P>
<Q>zld ... 9MM=</Q>
<DP>szi ... R1E=</DP>
<DQ>FEa ... 8MFE=</DQ>
<InverseQ>FUF ... wfA=</InverseQ>
<D>gEh ... m2gQ==</D>
</RSAKeyValue>
That has added newlines and is abbreviated for clarity.
Was that what you were expecting? If it was then I suggest that you check each of the included pieces of Base64 for errors. Alternatively, it may just be that your initial piece of Base64 is too long.
Fixed! It appears the error was a little misleading (or likely I was being stupid!) and it looks like it was NOT the plain text for encryption that was the cause of the exception but the password/passphrase.
I found another app I was using this method in and found the KEY was the problem. I was passing a plain text password in initially, then I changed this to a SHA-256 hash of the password when this was in fact not a valid key.
I added these two methods (the later of which I found on here):
public static string AesKeyFromPassword(string password, int keySize = Keysize.SymmetricKeyLength)
{
byte[] passwordByteArray = CreateKey(password);
var aesEncryption = new RijndaelManaged
{
KeySize = keySize,
BlockSize = 128,
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7,
Key = passwordByteArray
};
aesEncryption.GenerateIV();
string ivStr = Convert.ToBase64String(aesEncryption.IV);
string keyStr = Convert.ToBase64String(aesEncryption.Key);
string completeKey = ivStr + "," + keyStr;
return Convert.ToBase64String(Encoding.UTF8.GetBytes(completeKey));
}
private static byte[] CreateKey(string password)
{
var salt = new byte[] { 1, 2, 23, 234, 37, 48, 134, 63, 248, 4 };
const int Iterations = 9872;
using (var rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, salt, Iterations))
return rfc2898DeriveBytes.GetBytes(32);
}
This now takes the plain text password/phrase and generates a valid encryption key which I then use in the Encrypt method.
Thank for your help guys! I can now securely store the Private Key! :)

Resources