I'm trying to decrypt a message using Elliptic Curve Cryptograph ECDiffieHellman using KDFX963 as a key derivation function (KDF) (defined in ANSI-X9.63-KDF http://www.secg.org/sec1-v2.pdf)
But I'm struggling to get this done.
I pretty much need a method like :
DecryptMessage(string tokenFromPayload, string publicKeyStr, string privateKeyStr){
// Generate an ephemeral key from the public and private key
// Decrypt payload with the ephemeral key
}
Some extra information about the algorithm to decrypt the message :
Elliptic Curve : SECP384R1 ; NamedCurves.brainpoolP384r1
Cipher : AES Mode:GCM
Cipher Key Size : 32
Mac Size = 16
Key Derivation Function : ANSI-X9.63-KDF
Hash : Sha256 (lenght 32)
I have the Python code below that is doing what I need to decrypt the message, but I need this in C#.
import base64
import binascii
import json
import os
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.ciphers import algorithms, Cipher, modes
from cryptography import utils
from hashlib import sha256
from math import ceil
class BCAuthCrypto:
"""A class containing a number of handlers for the secp384r1 ECC algorithm in Python"""
curve = ec.SECP384R1()
cipher_key_size = 32
mac_size = 16
backend = default_backend()
def ITOSP(self, longint, length):
"""ITOSP, short for Integer-to-Octet-String Primitive, converts a non-negative integer
to an octet string of a specified length. This particular function is defined in the
PKCS #1 v2.1: RSA Cryptography Standard (June 14, 2002)
https://www.cryptrec.go.jp/cryptrec_03_spec_cypherlist_files/PDF/pkcs-1v2-12.pdf"""
hex_string = "%X" % longint
assert len(hex_string) <= 2 * length, "ITOSP function: Insufficient length for encoding"
return binascii.a2b_hex(hex_string.zfill(2 * length))
def KDFX963(self, inbyte_x, shared_data, key_length, hashfunct=sha256, hash_len=32):
"""KDFX963 is a key derivation function (KDF) that takes as input byte sequence inbyte_x
and additional shared data shared_data and outputs a byte sequence key of length
key_length. This function is defined in ANSI-X9.63-KDF, and this particular flavor of
KDF is known as X9.63. You can read more about it from:
http://www.secg.org/sec1-v2.pdf"""
assert key_length >= 0, "KDFX963 function: key_length should be positive integer"
k = key_length / float(hash_len)
k = int(ceil(k))
acc_str = ""
for i in range(1, k+1):
h = hashfunct()
h.update(inbyte_x)
h.update(self.ITOSP(i, 4))
h.update(shared_data)
acc_str = acc_str + h.hexdigest()
return acc_str[:key_length * 2]
def decrypt(self, cipher_text_b64, private_key):
"""Decrypt takes input base64-encoded data input cipher_text_b64 and private key
private_key and outputs plain text data, throws exception on error"""
cipher = base64.b64decode(cipher_text_b64)
ephemeral_key_len = ((self.curve.key_size + 7) // 8) * 2 + 1
ephemeral_key_numbers = ec.EllipticCurvePublicNumbers.from_encoded_point(self.curve, cipher[:ephemeral_key_len])
ephemeral_key = ephemeral_key_numbers.public_key(self.backend)
shared_key = private_key.exchange(ec.ECDH(), ephemeral_key)
V = cipher[:ephemeral_key_len]
K = binascii.unhexlify(self.KDFX963(shared_key, V, self.cipher_key_size + self.mac_size))
K1 = K[:self.cipher_key_size]
K2 = K[self.cipher_key_size:]
T = cipher[ephemeral_key_len:]
enc_data = T[:len(T) - self.mac_size]
tag = T[-self.mac_size:]
decryptor = Cipher(algorithms.AES(K1), modes.GCM(K2, tag), backend=self.backend).decryptor()
plain_text = decryptor.update(enc_data) + decryptor.finalize()
return plain_text
def decrypt_auth_token(tokenFromPayload, public_key_str, private_key_str):
"""Retrive the auth token and decrypt it, in a way that does not specify the name of the service."""
bc_crypto = BCAuthCrypto()
public_number = ec.EllipticCurvePublicNumbers.from_encoded_point(bc_crypto.curve, base64.b64decode(public_key_str))
private_number = ec.EllipticCurvePrivateNumbers(utils.int_from_bytes(base64.b64decode(private_key_str), "big"), public_number)
private_key = private_number.private_key(bc_crypto.backend)
token = bc_crypto.decrypt(tokenFromPayload, private_key)
print "token (decrypted): %s" % token
return token
Hoping there is a cryptography genius guy out there to help me, or a "Python To C#" expert.
Thanks.
Got my answer there Decrypt Apple Business Chat auth token
namespace AppleBusinessChat45
{
class Program
{
static void Main(string[] args)
{
var privateKey = "pX/BvdXXUdpC79mW/jWi10Z6PJb5SBY2+aqkR/qYOjqgakKsqZFKnl0kz10Ve+BP";
var token = "BDiRKNnPiPUb5oala31nkmCaXMB0iyWy3Q93p6fN7vPxEQSUlFVsInkJzPBBqmW1FUIY1KBA3BQb3W3Qv4akZ8kblqbmvupE/EJzPKbROZFBNvxpvVOHHgO2qadmHAjHSmnxUuxrpKxopWnOgyhzUx+mBUTao0pcEgqZFw0Y/qZIJPf1KusCMlz5TAhpjsw=";
// #####
// ##### Step 1
// #####
var decodedToken = Convert.FromBase64String(token);
var decodedEphemeralPublicKey = decodedToken.Take(97).ToArray();
var encodedEphemeralPublicKeyCheck = Convert.ToBase64String(decodedEphemeralPublicKey);
if (encodedEphemeralPublicKeyCheck != "BDiRKNnPiPUb5oala31nkmCaXMB0iyWy3Q93p6fN7vPxEQSUlFVsInkJzPBBqmW1FUIY1KBA3BQb3W3Qv4akZ8kblqbmvupE/EJzPKbROZFBNvxpvVOHHgO2qadmHAjHSg==")
throw new Exception("Public key check failed");
X9ECParameters curveParams = ECNamedCurveTable.GetByName("secp384r1");
ECPoint decodePoint = curveParams.Curve.DecodePoint(decodedEphemeralPublicKey);
ECDomainParameters domainParams = new ECDomainParameters(curveParams.Curve, curveParams.G, curveParams.N, curveParams.H, curveParams.GetSeed());
ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(decodePoint, domainParams);
var x = ecPublicKeyParameters.Q.AffineXCoord.ToBigInteger();
var y = ecPublicKeyParameters.Q.AffineYCoord.ToBigInteger();
if (!x.Equals(new BigInteger("8706462696031173094919866327685737145866436939551712382591956952075131891462487598200779332295613073905587629438229")))
throw new Exception("X coord check failed");
if (!y.Equals(new BigInteger("10173258529327482491525749925661342501140613951412040971418641469645769857676705559747557238888921287857458976966474")))
throw new Exception("Y coord check failed");
Console.WriteLine("Step 1 complete");
// #####
// ##### Step 2
// #####
var privateKeyBytes = Convert.FromBase64String(privateKey);
var ecPrivateKeyParameters = new ECPrivateKeyParameters("ECDHC", new BigInteger(1, privateKeyBytes), domainParams);
var privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(ecPrivateKeyParameters);
var ecPrivateKey = (ECPrivateKeyParameters) PrivateKeyFactory.CreateKey(privateKeyInfo);
IBasicAgreement agree = AgreementUtilities.GetBasicAgreement("ECDHC");
agree.Init(ecPrivateKey);
BigInteger sharedKey = agree.CalculateAgreement(ecPublicKeyParameters);
var sharedKeyBytes = sharedKey.ToByteArrayUnsigned();
var sharedKeyBase64 = Convert.ToBase64String(sharedKeyBytes);
if (sharedKeyBase64 != "2lvSJsBO2keUHRfvPG6C1RMUmGpuDbdgNrZ9YD7RYnvAcfgq/fjeYr1p0hWABeif")
throw new Exception("Shared key check failed");
Console.WriteLine("Step 2 complete");
// #####
// ##### Step 3
// #####
var kdf2Bytes = Kdf2(sharedKeyBytes, decodedEphemeralPublicKey);
var kdf2Base64 = Convert.ToBase64String(kdf2Bytes);
if (kdf2Base64 != "mAzkYatDlz4SzrCyM23NhgL/+mE3eGgfUz9h1CFPhZOtXequzN3Q8w+B5GE2eU5g")
throw new Exception("Kdf2 failed");
Console.WriteLine("Step 3 complete");
// #####
// ##### Step 4
// #####
var decryptionKeyBytes = kdf2Bytes.Take(32).ToArray();
var decryptionIvBytes = kdf2Bytes.Skip(32).ToArray();
var decryptionKeyBase64 = Convert.ToBase64String(decryptionKeyBytes);
var decryptionIvBase64 = Convert.ToBase64String(decryptionIvBytes);
if (decryptionKeyBase64 != "mAzkYatDlz4SzrCyM23NhgL/+mE3eGgfUz9h1CFPhZM=")
throw new Exception("Decryption key check failed");
if (decryptionIvBase64 != "rV3qrszd0PMPgeRhNnlOYA==")
throw new Exception("Decryption iv check failed");
var encryptedDataBytes = decodedToken.Skip(97).Take(decodedToken.Length - 113).ToArray();
var tagBytes = decodedToken.Skip(decodedToken.Length - 16).ToArray();
var encryptedDataBase64 = Convert.ToBase64String(encryptedDataBytes);
var tagBase64 = Convert.ToBase64String(tagBytes);
if (encryptedDataBase64 != "afFS7GukrGilac6DKHNTH6YFRNqjSlwSCpkXDRj+")
throw new Exception("Encrypted data check failed");
if (tagBase64 != "pkgk9/Uq6wIyXPlMCGmOzA==")
throw new Exception("Tag check failed");
KeyParameter keyParam = ParameterUtilities.CreateKeyParameter("AES", decryptionKeyBytes);
ParametersWithIV parameters = new ParametersWithIV(keyParam, decryptionIvBytes);
IBufferedCipher cipher = CipherUtilities.GetCipher("AES/GCM/NoPadding");
cipher.Init(false, parameters);
var resultBytes = cipher.DoFinal(encryptedDataBytes.Concat(tagBytes).ToArray());
var resultBase64 = Convert.ToBase64String(resultBytes);
var resultString = Strings.FromByteArray(resultBytes);
if (resultString != "xXTi32iZwrQ6O8Sy6r1isKwF6Ff1Py")
throw new Exception("Decryption failed");
Console.WriteLine("Step 4 complete");
Console.WriteLine(resultString);
Console.WriteLine();
Console.WriteLine("Done... press any key to finish");
Console.ReadLine();
}
static byte[] Kdf2(byte[] sharedKeyBytes, byte[] ephemeralKeyBytes)
{
var gen = new Kdf2BytesGenerator(new Sha256Digest());
gen.Init(new KdfParameters(sharedKeyBytes, ephemeralKeyBytes));
byte[] encryptionKeyBytes = new byte[48];
gen.GenerateBytes(encryptionKeyBytes, 0, encryptionKeyBytes.Length);
return encryptionKeyBytes;
}
}
}
Related
I’m encrypting sensitive data using the C# AES class, Aes.Create() method before saving to an SQLite DB. My base round trip is:
*Saving/retrieving each Byte[ ] without converting threw “The specified key is not a valid size for this algorithm.” when decrypting.
The process leverages the MSDN example and is working well:
I’m concerned, however, that cipher, key & IV values must all be stored/retrieved together and am attempting to disguise the values stored in the DB. I’ve tried three procedures to disguise/revert the relevant string values. Unfortunately adding a disguise/revert procedure throws the following error when decrypting:
System.Security.Cryptography.CryptographicException
HResult=0x80131430 Message=Padding is invalid and cannot be removed.
Source=System.Core
I've tried:
switching from Aes.Create to AesManaged; result: no change
setting Padding = PaddingMode.None; result: threw "'System.Security.Cryptography.CryptographicException' occurred in mscorlib.dll 'Length of the data to encrypt is invalid.'"
setting Padding = PaddingMode.Zeros; result: ran with no error but round trips returned incorrect & unreadable values (see image):
A number of posts (of the many, most of the issues are base process round trip failures) in this forum regarding this error state that the Padding error can be thrown for a variety of reasons only one of which is actually a Padding error. One post ( Padding is invalid and cannot be removed?, 4th reply sorted by Trending) states:
If the same key and initialization vector are used for encoding and
decoding, this issue does not come from data decoding but from data
encoding.
Since my keys & IVs are the same this looked hopeful. However, as the above image implies if I'm interpreting it correctly, changing encoding doesn't seem to help.
Since my base processs by itself works I suspect my case fall into the "not really a Padding error" category.
The disguise/revert procedures
First procedure
Remove the “=” character(s) at the end of each string since it’s a blatant indicator of “something important”
Split cipher, key & IV strings based on a random number into two halves
Swap the two halves’ positions and insert unique random text of random length between the halves
Create a random length “reversion code” capturing randomized split indices and resultant string lengths
Second procedure: same as the first but leave “=” character(s) intact.
Third procedure
Split the random text of three different random length strings
“Wrap” them around the cipher, key & IV strings
Create the “reversion code”
For all three procedures: upon retrieval from the DB reverse the process using the “reversion code”. In the third procedure the original strings should, theoretically, be “untouched”.
In all three cases output indicates Original and Reverted are identical (single run testing output sample):
The disguise/revert code
Code for the third procedure since it probably has the greatest probability of working. The Encrypt & Decrypt methods are included in case they're relevant:
private static string _cipherText = "", _keyText = "", _vectorText = "";
// Prior to saving: third procedure
public static void SaveSensitivePrep(string input, bool parent)
{
input = "secretID";
Console.WriteLine($"\nOriginal: {input}");
string startString = default, extractor = default;
int disguiseLen = 0, startStringLen = 0, doneStringLen = 0;
for (int i = 0; i < 3; i++)
{
using (Aes toEncrypt = Aes.Create())
{
byte[] cypherBytes = Encrypt(input, toEncrypt.Key, toEncrypt.IV);
string cipherText = Convert.ToBase64String(cypherBytes);
string keyText = Convert.ToBase64String(toEncrypt.Key);
string vectorText = Convert.ToBase64String(toEncrypt.IV);
if (i == 0) _cipherText = startString = cipherText;
else if (i == 1) _keyText = startString = keyText;
else if (i == 2) _vectorText = startString = vectorText;
int disguiseSelector = new Random().Next(101);
string disguiseTxt = default;
List<Disguises> disguise_DB = DBconnect.Disguises_Load();
for (int c = 0; c < disguise_DB.Count; c++)
{
if (c == disguiseSelector) { disguiseTxt = disguise_DB[c].Linkages; break; }
}
disguiseLen = disguiseTxt.Length;
int disguiseSplitIndex = new Random().Next(10, 22);
string startTxt = disguiseTxt.Substring(0, disguiseSplitIndex);
string endTxt = disguiseTxt.Substring(disguiseSplitIndex);
string doneString = $"{startTxt}{startString}{endTxt}";
doneStringLen = doneString.Length;
extractor = $"{disguiseSplitIndex}{disguiseLen}{startStringLen}" +
$"{disguiseSelector + new Random().Next(5000000)}";
if (i == 0) DBconnect.Encrypted_Update_CT(Host, extractor, doneString);
else if (i == 1) DBconnect.Encrypted_Update_CK(Host, extractor, doneString);
else if (i == 2) DBconnect.Encrypted_Update_CV(Host, extractor, doneString);
}
}
}
// After retrieval: third procedure
public static string LoadSensitive(string input, bool parent)
{
string cipherText = "", keyText = "", vectorText = "";
for (int i = 0; i < 3; i++)
{
string extractor = "";
int disguiseLeft, disguiseRight;
List<EncryptedClass> host = DBconnect.Encrypted_Load(Host);
string doneString = "";
if (i == 0) { extractor = host[0].RCC; doneString = host[0].cipherText; }
else if (i == 1) { extractor = host[0].RCK; doneString = host[0].keyText; }
else if (i == 2) { extractor = host[0].RCV; doneString = host[0].vectorText; }
string disguiseSplitter = extractor.Substring(0, 2);
string disguiseLen_t = extractor.Substring(2, 2);
string startStringLen_t = extractor.Substring(4, 2);
int.TryParse(disguiseSplitter, out int indx);
int.TryParse(disguiseLen_t, out int disguiseLen);
int.TryParse(startStringLen_t, out int startStringLen);
disguiseRight = disguiseLen - indx;
disguiseLeft = disguiseLen - disguiseRight;
string e_extrctd = doneString.Substring(disguiseLeft, startStringLen);
if (i == 0) cipherTxt = revertedString;
else if (i == 1) keyTxt = revertedString;
else vectorTxt = revertedString;
}
byte[] cypher = Convert.FromBase64String(cipherText);
byte[] key = Convert.FromBase64String(keyText);
byte[] vector = Convert.FromBase64String(vectorText);
return Decrypt(cypher, key, vector);
}
static byte[] Encrypt(string input, byte[] key, byte[] iv)
{
// NULL/Length > 0 checks.
byte[] encrypted;
using (Aes input_e = Aes.Create())
{
input_e.Key = key;
input_e.IV = iv;
ICryptoTransform ict = input_e.CreateEncryptor(input_e.Key, input_e.IV);
using (MemoryStream msInput = new MemoryStream())
{
using (CryptoStream csInput = new CryptoStream(msInput, ict, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csInput))
{
swEncrypt.Write(input);
}
encrypted = msInput.ToArray();
}
}
}
return encrypted;
}
static string Decrypt(byte[] cipherText, byte[] keyText, byte[] vectorText)
{
// NULL/Length > 0 checks.
string roundtrip = null;
using (Aes output_e = Aes.Create())
{
output_e.Key = keyText;
output_e.IV = vectorText;
ICryptoTransform ict = output_e.CreateDecryptor(output_e.Key, output_e.IV);
using (MemoryStream msOutput = new MemoryStream(cipherText))
{
using (CryptoStream csOutput = new CryptoStream(msOutput, ict, CryptoStreamMode.Read))
{
using (StreamReader srOutput = new StreamReader(csOutput))
{
roundtrip = srOutput.ReadToEnd();
}
}
}
}
return roundtrip;
}
Any guidance on how/why the disguise/revert is getting deformed will be appreciated.
EDIT: scope clarification: the security functionality in this instance is for a Winforms/SQLite app which has an FTP component (WinSCP). The app's high level objective is a desktop tool streamlining TLS/SSL Certificate request/issuance via Let's Encrypt. The primary security objective is protecting FTP session credentials should an unauthorized & semi-knowledgeable person gain access to the DB. The secondary security objective is to keep it as simple as possible.
I have this binary content of file it's RSA public key generated with Java.
¬í sr java.math.BigIntegerŒüŸ©;û I bitCountI bitLengthI firstNonzeroByteNumI lowestSetBitI signum[ magnitudet [Bxr java.lang.Number†¬•”à‹ xpÿÿÿÿÿÿÿÿÿÿÿþÿÿÿþ ur [B¬óøTà xp °^¾ùŠÖÂá}ÈþŽ–†ÁÃêsÀ»púŒaré…íè8NKŒ lH réöˆMj'cmQë>À?y‘Ǩj£ã<ʇ²ÞƒÕ4<Qga÷£#I“’[ØÜ€äºbwYòEûŸ1™àô©ñº…ýÙ£?³â+¶E #>ò̆ëÜ"nàú¾Ñt”²:r {
gÁ:ÿò¶ÌHZ`®âàtG;÷ŠzýIô÷c×ä5—Mx¾+Ë3Û#3>c¶jŒ GvbD¿SGI.˜ã;™sC¾ÝÇôôJ õ½-‹î”2ò÷…®‡¢ZƒtÃÑ#âÉÑ/|¹²5ñ’µZcxsq ~ ÿÿÿÿÿÿÿÿÿÿÿþÿÿÿþ uq ~ x
how to get Modulus and exponent from this content using c# and dotnet framework (not core).
Yes, It is a Java public key. I could not found any function to read this key and get thye RSAparameters.
I used this Java code to get the Modulus and exponenet with success. but when use it in c# to create public key I always get "Parameter Incorrect" on windows 2012 server.
Java Code
BigInteger modulo = null, exposant = null;
try {
ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(nomFichier)));
modulo = (BigInteger) ois.readObject();
exposant = (BigInteger) ois.readObject();
} catch(IOException e) {
System.err.println("Erreur lors de la lecture de la clé : " + e);
System.exit(-1);
} catch(ClassNotFoundException e) {
System.err.println("Fichier de clé incorrect : " + e);
System.exit(-1);
}
PublicKey clePublique = null;
try {
RSAPublicKeySpec specification = new RSAPublicKeySpec(modulo, exposant);
KeyFactory usine = KeyFactory.getInstance("RSA");
clePublique = usine.generatePublic(specification);
} catch(NoSuchAlgorithmException e) {
System.err.println("Algorithme RSA inconnu : " + e);
System.exit(-1);
} catch(InvalidKeySpecException e) {
System.err.println("Spécification incorrecte : " + e);
System.exit(-1);
}
return clePublique;
c# code
CspParameters parms = new CspParameters();
parms.Flags = CspProviderFlags.NoFlags;
parms.KeyContainerName = Guid.NewGuid().ToString().ToUpperInvariant();
parms.ProviderType = ((Environment.OSVersion.Version.Major > 5) || ((Environment.OSVersion.Version.Major == 5) && (Environment.OSVersion.Version.Minor >= 1))) ? 0x18 : 1;
// Modulus and Exponent are from Java
BigInteger modulusInt = BigInteger.Parse("22264662665581503581958564757951433642071042765180847835607911485241107337546440921333481060699040617948699619143852514883961242740412934026407809467632924273132875711353979878192151539324021302381664216461707890134565390428705286450284770084749935209949622966373476605854194876749604260497977835251836811598929536162941397609744200129752240619098969605506158549850626894587090735501922815967991426385372742427057780208805955531394006831299305270496208054419166650641070163691999735656859181256967560193451396805797552205282299420633106177676710034732661564145564177377055134205294304271572837276495701834203634424419");
BigInteger ExponentInt = BigInteger.Parse("65537");
csp = new RSACryptoServiceProvider(parms);
_publicKey = new RSAParameters();
_publicKey = csp.ExportParameters(false);
byte[] exponent = ExponentInt.ToByteArray();
byte[] modulus = modulusInt.ToByteArray();
_publicKey.Modulus = modulus;
_publicKey.Exponent = exponent;
csp.PersistKeyInCsp = true;
csp.ImportParameters(_publicKey);
byte[] token = csp.Encrypt(Convert.FromBase64String(txtPrivateKey.Text), true);
Could you please help me to read the binary Java key and import parameters to my RSA with c# (because the key every month will be changed).
this is Modulus and exponenet Base64
Modulus key: Y1q1kvE1srkafC/RyeJA0cN0GRqDWqKHrguF9/IylO6LLb31AEr0DvTHARl/3b5Dcw6ZO+OYLklHU79EYnZHCYxqtgtjPjMOI9sYM8srvnhNlzXk12P39En9eor3O0d04JDirmBaSMwBtoHy/zrBZyANe6ByOrKUdNG++uBuItwd64bM8j4RQBUgRbYr4rM/o9n9hbrxqfTgmTGf+0XyWRt3YrrkgNwX2FuSGpNJI6MR939hZ1E8NNWDj96yh8oDPOOjagKox5F5P4/APutRbWMnak2I9ulyIEhsCRiMS0446O2F6XJhF4z6cLvAc+rDwYaWAo7+yH3hwtaK+b5esAA=
Exponent key: AQAB
the error I got it on the Windows server 2012 R2 is from here
csp.ImportParameters(_publicKey);
this function work on Windows 10
I'm trying to encrypt a license file in PHP and decrypt it for verification in an application in C# Sharp but I'm stuck, tried different examples but they either don't work or require a 1MB library
Generating a key pair via:
$dn = array(
"countryName" => "UA",
"stateOrProvinceName" => "test",
"localityName" => "test1",
"organizationName" => "test2",
"organizationalUnitName" => "test3",
"commonName" => "test4",
"emailAddress" => "test5#gmail.com"
);
$privkey = openssl_pkey_new(array(
"private_key_bits" => 4096,
"private_key_type" => OPENSSL_KEYTYPE_RSA,
));
// Create CSR
$csr = openssl_csr_new($dn, $privkey, array('digest_alg' => 'sha512'));
// Create a self-signed certificate with a lifetime of 365 days
$x509 = openssl_csr_sign($csr, null, $privkey, $days=365, array('digest_alg' => 'sha512'));
// Save in the file
$filePrivKey = 'private.key';
openssl_pkey_export_to_file($privkey, $filePrivKey);
// CSR - Export to file
$csrFile = 'cert.crt';
openssl_csr_export_to_file($csr, $csrFile);
//$x509csrFile crt
$x509csrFile = 'x509cert.crt';
openssl_x509_export_to_file($x509 ,$x509csrFile);
// Publick key
$pub_key = openssl_pkey_get_public(file_get_contents('x509cert.crt'));
$keyData = openssl_pkey_get_details($pub_key);
file_put_contents('key.pub', $keyData['key']);
I encrypt in PHP using a private key
// I’ll get the key to the file
$pathPriv = 'private.key';
$PrivKey = file_get_contents($pathPriv);
$LicenseKey = '05.05.2020 | example#gmail.com | Nikname';
openssl_private_encrypt($LicenseKey, $encrypted, $PrivKey);
$message = base64_encode($encrypted);
DecryptKey($message);
Trying to decrypt in C#.NET Framework
public MainWindow()
{
InitializeComponent();
string pubkey = System.IO.File.ReadAllText(#"publicKey.xml");
string message = "bPqM8JCgcGcBhWcJAxZyt6bqngL1POdQuehtWjsk3zE2m6YekpZdRu+c7B9lcLVLFqZ9K88uSC6o/7kUCBDDsxsZH8pvX7Yn11YSfUOgjfJJlThipibbmJl7KryQdYUxD8YEvUf0x2Q2ZlMaeMbKVk9IOSo87LKvSV5US7QmEz23ZiHiwQXNbI+txdwTHN8jyH2Byy7uHDz1BYwgpkmUk00BoXxO4kkz8P5Xgq5OHqisr2j/RvO2MkLHusOyAJYoXlyq/Fqn+EzFf4IPsdd/h5LQosd+Pyha9vR4RXJN+RT87KSXx0ZZuvQqUfYm3U6QzWzxBlPk1gOZ0nG2BWDcK9cJJWbKPn65K6c+U5109n7FRLQE7nnVPS9zU7D9sKm0+rOUGh0wDgClxfMsspESIpanuc/eMUZvIPZ98omNFny+r8Jf2aW4fcm4iydUMk3uRBIS7j1NVd4C5pR/vHTqSVcZDxiMRsLcAZ7PFwmjPik76eOmcxssiMS9CavOKe2h85uFhEfsaMCFim1lJ5rtznqc60l1iGa1/Pq3r0TdG0/RfIUOkZxr46HIwwXd6rfzGu2zwMMxl4Jfv6iWvpQk+DNmXv0Yw1w/Q9PF0i51mEen+z5nQSRJQsl8cmMBukOGTJ+X3ZELe848kni1luE20Icz/DSwv26QVVY6dZqiGV4=";
byte[] ciphertext = Convert.FromBase64String(message);
string decodedString = Encoding.UTF8.GetString(ciphertext);
}
public string decrypt(string elementToDesencrypt)
{
// string pem = System.IO.File.ReadAllText(pathPublicKey);
string pem = System.IO.File.ReadAllText(#"publicKey.xml");
byte[] Buffer = GetPublicKey();
System.Security.Cryptography.RSACryptoServiceProvider rsa = new System.Security.Cryptography.RSACryptoServiceProvider();
System.Security.Cryptography.RSAParameters rsaParam = rsa.ExportParameters(false);
rsaParam.Modulus = Buffer;
rsa.ImportParameters(rsaParam);
byte[] encryptedMessageByte = rsa.Decrypt(Convert.FromBase64String(elementToDesencrypt), false);
return Convert.ToBase64String(encryptedMessageByte);
}
I am getting an exception "The data size to be decrypted exceeds the maximum for this module, 512 bytes"
I'm trying to generate the same password hash using NodeJS crypto library and C# Rfc2898DeriveBytes. The NodeJs implementation doesn't generate the same key when using the salt generated from C#. What am I doing wrong?
In C#:
public static string HashPassword(string password)
{
// random khóa
using (var rngCryp = new RNGCryptoServiceProvider())
{
var salt = new byte[SaltBytes];
rngCryp.GetBytes(salt);
// Hash the password and encode the parameters
byte[] hash = Rfc2898Deriver(password, salt, Pbkdf2Iterations, HashBytes);
return Pbkdf2Iterations + ":" + Convert.ToBase64String(salt) + ":" + Convert.ToBase64String(hash);
}
}
private static byte[] Rfc2898Deriver(string password, byte[] salt, int iterations, int outputMaxByte)
{
using (var deriveBytes = new Rfc2898DeriveBytes(password, salt))
{
deriveBytes.IterationCount = iterations;
return deriveBytes.GetBytes(outputMaxByte);
}
}
In NodeJs:
export const hash = (text, salt) => new Promise((resolve, reject) => {
crypto.pbkdf2(text, salt, iterations, bytes, 'sha256', function (err, derivedKey) {
if (err) { reject(err) }
else {
//return Pbkdf2Iterations + ":" + Convert.ToBase64String(salt) + ":" + Convert.ToBase64String(hash);
var hash = new Buffer(derivedKey).toString('base64');
var pass = `${iterations}:${salt}:${hash}`
resolve(pass);
}});})
and use like that:
var a = Buffer.from("qcMqVYE0EzAU9Uz+mQxBaKFICG1vR1iq", 'base64')
var a0 = new Buffer("qcMqVYE0EzAU9Uz+mQxBaKFICG1vR1iq")
var pas1 = new Buffer('AL7h8Jx4r8a8PjS5', 'base64')
hash(pas1,a0).then(pass => {
console.log("pass: ", pass)
const hashes = crypto.getHashes();
console.log(hashes); // ['DSA', 'DSA-SHA', 'DSA-SHA1', ...]
res.send(pass + "\n1000:qcMqVYE0EzAU9Uz+mQxBaKFICG1vR1iq:RkdpgAcpijFqYgVxBCvJugMXqnt4j5f3")
})
As you see, hass pass in C# and Nodejs is different.
Node ->
1000:qcMqVYE0EzAU9Uz+mQxBaKFICG1vR1iq:D19SUxg6AQxgSLe7YXISPWPvgIoR6BEw
C# ->
1000:qcMqVYE0EzAU9Uz+mQxBaKFICG1vR1iq:RkdpgAcpijFqYgVxBCvJugMXqnt4j5f3
I had a very similar question and actually your findings helped me a lot.
Looks like the only problem you had is the wrong hashing algorithm that you passed to pbkdf2 function.
Looks like Rfc2898DeriveBytes uses SHA1 by default. So you should have used the smth like that in node:
crypto.pbkdf2(text, salt, iterations, bytes, 'sha1', (err, key) => {
console.log(key.toString('hex'));
});
Here i entered fix salt, you can also generate random string of 16. You can change length also instead of mine "32".
var crypto = require('crypto');
salt = '1234123412341234';
saltString = new Buffer(salt).toString('hex');
var password = 'welcome';
var nodeCrypto = crypto.pbkdf2Sync(new Buffer(password), new Buffer(saltString, 'hex'), 1000, 32, 'sha1');
var hashInHex="00"+saltString+nodeCrypto.toString('hex').toUpperCase();
var FinalHash = Buffer.from(hashInHex, 'hex').toString('base64')
console.log("saltInHex: "+saltString);
console.log("FinalHashInBase64: "+FinalHash);
To match stored hash password with user inpur password use bellow code :
// NodeJS implementation of crypto, I'm sure google's
// cryptoJS would work equally well.
var crypto = require('crypto');
// The value stored in [dbo].[AspNetUsers].[PasswordHash]
var hashedPwd = "AGYzaTk3eldHaXkxbDlkQmn+mVJZEjd+0oOcLTNvSQ+lvUQIF1u1CNMs+WjXEzOYNg==";
var hashedPasswordBytes = new Buffer(hashedPwd, 'base64');
var hexChar = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];
var saltString = "";
var storedSubKeyString = "";
// build strings of octets for the salt and the stored key
for (var i = 1; i < hashedPasswordBytes.length; i++) {
if (i > 0 && i <= 16) {
saltString += hexChar[(hashedPasswordBytes[i] >> 4) & 0x0f] + hexChar[hashedPasswordBytes[i] & 0x0f]
}
if (i > 0 && i > 16) {
storedSubKeyString += hexChar[(hashedPasswordBytes[i] >> 4) & 0x0f] + hexChar[hashedPasswordBytes[i] & 0x0f];
}
}
// password provided by the user
var password = 'vish#123';
// TODO remove debug - logging passwords in prod is considered
// tasteless for some odd reason
console.log('cleartext: ' + password);
console.log('saltString: ' + saltString);
console.log('storedSubKeyString: ' + storedSubKeyString);
// This is where the magic happens.
// If you are doing your own hashing, you can (and maybe should)
// perform more iterations of applying the salt and perhaps
// use a stronger hash than sha1, but if you want it to work
// with the [as of 2015] Microsoft Identity framework, keep
// these settings.
var nodeCrypto = crypto.pbkdf2Sync(new Buffer(password), new Buffer(saltString, 'hex'), 1000, 256, 'sha1');
// get a hex string of the derived bytes
var derivedKeyOctets = nodeCrypto.toString('hex').toUpperCase();
console.log("hex of derived key octets: " + derivedKeyOctets);
// The first 64 bytes of the derived key should
// match the stored sub key
if (derivedKeyOctets.indexOf(storedSubKeyString) === 0) {
console.info("passwords match!");
} else {
console.warn("passwords DO NOT match!");
}
There is an issue came into my mind.
Is there any common way to make an app running only when there is an installed certificate in the system. And I want such a certificate to be issued and verified by my self-signed certificate?
I can get a certificate by it's name from the storage, but how do I make sure such a certificate is signed by my self-signed certificate and nobody have issued a certificate with the same name and replaced the one in the local storage?
Or in other words, how do I make sure the certificate which signed the certificate at local storage is not a forged one?
I'm sorry if its not correct and|or clear question, but I'll be happy to have help regarding it.
Very good question indeed.
There is always a possibility of the end-user creating a valid certificate chain with your subject name and as the issuer, another for the issuer ceritificate, all up to the root.
What they canot do is to sign those certifcates with the issuer certificate's private key.
Therefore, the code below loads the application certificate from the personal certificate store of the current user, then, loads the issuer certificate of the issuer from the resources and verifies the signature on the application certificate installed on the client machine using the public key of the issuer certificate.
In my source code, the issuer certificate is added to the resources with the key IssuerCertificate
I am actually fond of coming out with a solution like this.
In the code I mention an encoding ASN.1. Check it here if you need
static void Main(string[] args)
{
string expectedSubjectName = "My Application";
X509Certificate2 issuerCertificate = new X509Certificate2(Resource1.IssuerCertificate);
string expectedIssuerName = issuerCertificate.Subject;
bool result = VerifyCertificateIssuer(expectedSubjectName, expectedIssuerName, issuerCertificate);
}
private static void ThrowCertificateNotFoundException(string expectedSubjectName, string expectedIssuerName, bool isThumbprintMismatch)
{
if (isThumbprintMismatch)
{
// Notification for possible certificate forgery
}
throw new SecurityException("A certificate with subject name " + expectedSubjectName + " issued by " + expectedIssuerName + " is required to run this application");
}
private static X509Certificate2 GetCertificate(string expectedSubjectName, string expectedIssuerName)
{
X509Store personalCertificateStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
personalCertificateStore.Open(OpenFlags.ReadOnly);
X509CertificateCollection certificatesBySubjectName = personalCertificateStore.Certificates.Find(X509FindType.FindBySubjectName, expectedSubjectName, true);
if (certificatesBySubjectName.Count == 0)
{
ThrowCertificateNotFoundException(expectedSubjectName, expectedIssuerName, false);
}
X509Certificate2 matchingCertificate = null;
foreach (X509Certificate2 certificateBySubjectName in certificatesBySubjectName)
{
if (certificateBySubjectName.Issuer == expectedIssuerName)
{
matchingCertificate = certificateBySubjectName;
break;
}
}
if (matchingCertificate == null)
{
ThrowCertificateNotFoundException(expectedSubjectName, expectedIssuerName, false);
}
return matchingCertificate;
}
private static bool VerifyCertificateIssuer(string expectedSubjectName, string expectedIssuerName, X509Certificate2 issuerCertificate)
{
X509Certificate2 matchingCertificate = GetCertificate(expectedSubjectName, expectedIssuerName);
X509Chain chain = new X509Chain();
chain.Build(matchingCertificate);
// bool x = Encoding.ASCII.GetString(chain.ChainElements[1].Certificate.RawData) == Encoding.ASCII.GetString(issuerCertificate.RawData);
byte[] certificateData = matchingCertificate.RawData;
MemoryStream asn1Stream = new MemoryStream(certificateData);
BinaryReader asn1StreamReader = new BinaryReader(asn1Stream);
// The der encoded certificate structure is like this:
// Root Sequence
// Sequence (Certificate Content)
// Sequence (Signature Algorithm (like SHA256withRSAEncryption)
// Sequence (Signature)
// We need to decode the ASN.1 content to get
// Sequence 0 (which is the actual certificate content that is signed by the issuer, including the sequence definition and tag number and length)
// Sequence 2 (which is the signature. X509Certificate2 class does not give us that information. The year is 2015)
// Read the root sequence (ignore)
byte leadingOctet = asn1StreamReader.ReadByte();
ReadTagNumber(leadingOctet, asn1StreamReader);
ReadDataLength(asn1StreamReader);
// Save the current position because we will need it for including the sequence header with the certificate content
int sequence0StartPosition = (int)asn1Stream.Position;
leadingOctet = asn1StreamReader.ReadByte();
ReadTagNumber(leadingOctet, asn1StreamReader);
int sequence0ContentLength = ReadDataLength(asn1StreamReader);
int sequence0HeaderLength = (int)asn1Stream.Position - sequence0StartPosition;
sequence0ContentLength += sequence0HeaderLength;
byte[] sequence0Content = new byte[sequence0ContentLength];
asn1Stream.Position -= 4;
asn1StreamReader.Read(sequence0Content, 0, sequence0ContentLength);
// Skip sequence 1 (signature algorithm) since we don't need it and assume that we know it because we own the issuer certificate
// This sequence, containing the algorithm used during the signing process IS HIDDEN FROM US BY DEFAULT. The year is 2015.
// What should have been done for real is, to get the algorithm ID (hash algorithm and asymmetric algorithm) and to use those
// algorithms during the verification process
leadingOctet = asn1StreamReader.ReadByte();
ReadTagNumber(leadingOctet, asn1StreamReader);
int sequence1ContentLength = ReadDataLength(asn1StreamReader);
byte[] sequence1Content = new byte[sequence1ContentLength];
asn1StreamReader.Read(sequence1Content, 0, sequence1ContentLength);
// Read sequence 2 (signature)
// The actual signature of the certificate IS HIDDEN FROM US BY DEFAULT. The year is 2015.
leadingOctet = asn1StreamReader.ReadByte();
ReadTagNumber(leadingOctet, asn1StreamReader);
int sequence2ContentLength = ReadDataLength(asn1StreamReader);
byte unusedBits = asn1StreamReader.ReadByte();
sequence2ContentLength -= 1;
byte[] sequence2Content = new byte[sequence2ContentLength];
asn1StreamReader.Read(sequence2Content, 0, sequence2ContentLength);
// At last, we have the data that is signed and the signature.
bool verificationResult = ((RSACryptoServiceProvider)issuerCertificate.PublicKey.Key)
.VerifyData
(
sequence0Content,
CryptoConfig.MapNameToOID("SHA256"),
sequence2Content
);
return verificationResult;
}
private static byte[] ReadTagNumber(byte leadingOctet, BinaryReader inputStreamReader)
{
List<byte> byts = new List<byte>();
byte temporaryByte;
if ((leadingOctet & 0x1F) == 0x1F) // More than 1 byte is used to specify the tag number
{
while (((temporaryByte = inputStreamReader.ReadByte()) & 0x80) > 0)
{
byts.Add((byte)(temporaryByte & 0x7F));
}
byts.Add(temporaryByte);
}
else
{
byts.Add((byte)(leadingOctet & 0x1F));
}
return byts.ToArray();
}
private static int ReadDataLength(BinaryReader inputStreamReader)
{
byte leadingOctet = inputStreamReader.ReadByte();
if ((leadingOctet & 0x80) > 0)
{
int subsequentialOctetsCount = leadingOctet & 0x7F;
int length = 0;
for (int i = 0; i < subsequentialOctetsCount; i++)
{
length <<= 8;
length += inputStreamReader.ReadByte();
}
return length;
}
else
{
return leadingOctet;
}
}
private static byte[] GetTagNumber(byte leadingOctet, BinaryReader inputStreamReader, ref int readBytes)
{
List<byte> byts = new List<byte>();
byte temporaryByte;
if ((leadingOctet & 0x1F) == 0x1F) // More than 1 byte is used to specify the tag number
{
while (((temporaryByte = inputStreamReader.ReadByte()) & 0x80) > 0)
{
readBytes++;
byts.Add((byte)(temporaryByte & 0x7F));
}
byts.Add(temporaryByte);
}
else
{
byts.Add((byte)(leadingOctet & 0x1F));
}
return byts.ToArray();
}