How do we translate the following java into C# for .Net Framework 4.8?
private static String getBearerToken(String publicKeyBase64, String apiKey)
{
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
Cipher cipher = Cipher.getInstance("RSA");
byte[] encodedPublicKey = Base64.decodeBase64(publicKeyBase64);
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(encodedPublicKey);
PublicKey pk = keyFactory.generatePublic(publicKeySpec);
cipher.init(Cipher.ENCRYPT_MODE, pk);
byte[] encryptedApiKey = Base64.encodeBase64(cipher.doFinal(apiKey.getBytes("UTF-8")));
return new String(encryptedApiKey, "UTF-8");
}
I have tried fiddling with the following (with BouncyCastle package):
public static string GetBearerToken(string publicKeyBase64, string apiKey)
{
var key = Convert.FromBase64String(publicKeyBase64);
var info = SubjectPublicKeyInfo.GetInstance(key);
var pk = PublicKeyFactory.CreateKey(info);
var x = ???(pk);
using (var rsa = new RSACryptoServiceProvider(???))
{
var parameters = new RSAParameters()
{
Modulus = x.???,
Exponent = x.???
};
rsa.ImportParameters(parameters);
var data = Encoding.UTF8.GetBytes(apiKey);
var encryptedBytes = rsa.Encrypt(data, true);
return Convert.ToBase64String(encryptedBytes);
}
}
I also tried this, which runs but does not produce the expected result in the test case:
public static string GetBearerToken(string publicKeyBase64, string apiKey)
{
var keyBytes = Convert.FromBase64String(publicKeyBase64);
var info = SubjectPublicKeyInfo.GetInstance(keyBytes);
var keyParameter = PublicKeyFactory.CreateKey(info);
var encryptEngine = new RsaEngine();
encryptEngine.Init(true, keyParameter);
var dataToEncrypt = Encoding.UTF8.GetBytes(apiKey);
var encryptedBytes = encryptEngine.ProcessBlock(dataToEncrypt, 0, dataToEncrypt.Length);
return Convert.ToBase64String(encryptedBytes);
}
And here is the unit test I'm trying to get passing:
[TestMethod]
public void Utils_GetBearerToken()
{
var publicKey = "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAszE+xAKVB9HRarr6/uHYYAX/RdD6KGVIGlHv98QKDIH26ldYJQ7zOuo9qEscO0M1psSPe/67AWYLEXh13fbtcSKGP6WFjT9OY6uV5ykw9508x1sW8UQ4ZhTRNrlNsKizE/glkBfcF2lwDXJGQennwgickWz7VN+AP/1c4DnMDfcl8iVIDlsbudFoXQh5aLCYl+XOMt/vls5a479PLMkPcZPOgMTCYTCE6ReX3KD2aGQ62uiu2T4mK+7Z6yvKvhPRF2fTKI+zOFWly//IYlyB+sde42cIU/588msUmgr3G9FYyN2vKPVy/MhIZpiFyVc3vuAAJ/mzue5p/G329wzgcz0ztyluMNAGUL9A4ZiFcKOebT6y6IgIMBeEkTwyhsxRHMFXlQRgTAufaO5hiR/usBMkoazJ6XrGJB8UadjH2m2+kdJIieI4FbjzCiDWKmuM58rllNWdBZK0XVHNsxmBy7yhYw3aAIhFS0fNEuSmKTfFpJFMBzIQYbdTgI28rZPAxVEDdRaypUqBMCq4OstCxgGvR3Dy1eJDjlkuiWK9Y9RGKF8HOI5a4ruHyLheddZxsUihziPF9jKTknsTZtF99eKTIjhV7qfTzxXq+8GGoCEABIyu26LZuL8X12bFqtwLAcjfjoB7HlRHtPszv6PJ0482ofWmeH0BE8om7VrSGxsCAwEAAQ==";
var apiKey = "aaaab09uz9f3asdcjyk7els777ihmwv8";
var expectedToken = "rfNjFso4uJbzhwl8E9vizqmHEuD7XDmPqfsRx1L62UoTmURGGLAGgJSl9lCPbgy03Q7NwozFYD4r9BFQY5QpvErHximBDU8HE25urVahm0HnB8VyCIobs684XGSN4GjdequePDrG6xUAxxpvmhqZRlGt1tUjUBeBg6kYqp4EnKHsiaBtvd0THGLZbefpT6UaShASQWYNiEPwEon5wtUMaDwnyQEazDu1H2ieN3r8cCVM3hsak59J/1MP07FQjdFbxdCLfA0DuxgpeKpvLs7WrA767WJSB1QZy7hcP1igSGRfd7Zrp6E7gIukdpC0DApqPKa4XsNTo2AMpG4AwiET2WeKvHn539gbwREXf79kZlYdFDCgTc0Zs7OfDx5ZXMCBKHOS/H3tVFJqXTfEfIF5LOzrFU5pPE0HeNBV0Q2vm8qRwQX0RijnvMOGpdcmXb0qoph4oy8Mj+vjRfFRboMAafttDozBhRmWEmeBB3EjYASm1fToQp5ey6ltCiEt8rjL5PlexxB0u3u2LVJQcDzMVNiiq10t1xyw8qtc6BMOyrKVlIANWglRYOKr9saVBVvDFUcCfsghMjUTDeAwHom4A3cSDWmVlNF9Vs/WqCoUzjQCV0BFPDzeAUbQqt7h7OgFno/+D9n5j1eMro0aXbbHNx71u8YmgPJhdixzFhxM1Pw=";
var token = Utils.GetBearerToken(publicKey, apiKey);
Assert.AreEqual(expectedToken, token, "GetBearerToken");
}
I have figured out a solution from a very similar question: .NET equivalent of Java KeyFactory.getInstance "RSA"/"RSA/ECB/PKCS1Padding"
I also pasted the original java code into jdoodle (https://www.jdoodle.com/online-java-compiler-ide/) to see what it actually does - encrypts with randomization therefore the result is different every time and cannot be unit tested as originally presumed (contrary to the API documentation page, therefore always confirm the assumptions first!)
The following produces randomized results but so far seems to be accepted by the API (uses BouncyCastle package):
public static string GetBearerToken(string publicKeyBase64, string apiKey)
{
var keyBytes = Convert.FromBase64String(publicKeyBase64);
var keyParameter = PublicKeyFactory.CreateKey(keyBytes);
var rsaKeyParameters = (RsaKeyParameters)keyParameter;
var rsaParameters = new RSAParameters
{
Modulus = rsaKeyParameters.Modulus.ToByteArrayUnsigned(),
Exponent = rsaKeyParameters.Exponent.ToByteArrayUnsigned()
};
using (var rsa = new RSACryptoServiceProvider())
{
rsa.ImportParameters(rsaParameters);
var dataToEncrypt = Encoding.UTF8.GetBytes(apiKey);
var encryptedBytes = rsa.Encrypt(dataToEncrypt, false);
return Convert.ToBase64String(encryptedBytes);
}
}
this project in github is a example of this usage https://github.com/RomuloSantanaFadami/fadamipay-autorizador-exemplo-rsa
if you have a problem with a public key formating, try use StringBuilder to make her.
I use like this
internal partial class Criptografia{
private string ChavePublica = String.Empty;
public Criptografia()
{
//if (String.IsNullOrEmpty(PertoBus.Program.PublicKeyQrCode))
//{
StringBuilder chavePublica = new StringBuilder();
chavePublica.AppendLine("-----BEGIN RSA PUBLIC KEY-----");
chavePublica.AppendLine("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
chavePublica.AppendLine("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
chavePublica.AppendLine("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=");
chavePublica.AppendLine("-----END RSA PUBLIC KEY-----");
ChavePublica = chavePublica.ToString();
//}
//else
// ChavePublica = PertoBus.Program.PublicKeyQrCode;
}
public string RsaCriptografarComChavePublica(string pTexto)
{
var bytesToEncrypt = Encoding.UTF8.GetBytes(pTexto);
var encryptEngine = new Pkcs1Encoding(new RsaEngine());
using (var txtreader = new StringReader(ChavePublica.ToString()))
{
var keyParameter = (AsymmetricKeyParameter)new PemReader(txtreader).ReadObject();
encryptEngine.Init(true, keyParameter);
}
var encrypted = Convert.ToBase64String(encryptEngine.ProcessBlock(bytesToEncrypt, 0, bytesToEncrypt.Length));
return encrypted;
}
}
Related
've been trying to port some NodeJS code to Java in order to make HMAC signed requests to an API. I've been able to simplify the code to a point were encoding is clearly affecting the output.
Here's my code in JavaScript:
var APIKey = "secret";
var secretByteArray = CryptoJS.enc.Base64.parse(APIKey);
var hash = CryptoJS.HmacSHA256("Message", secretByteArray);
var hashInBase64 = CryptoJS.enc.Base64.stringify(hash);
//alert(hashInBase64);
document.write(hashInBase64);
Here's the Java Code:
try {
String secret = "secret";
String message = "Message";
byte[] secretByteArray = Base64.getEncoder().encode(secret.getBytes());
//byte[] secretByteArray = Base64.encodeBase64(secret.getBytes("utf-8"), true);
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(secretByteArray, "HmacSHA256");
sha256_HMAC.init(secret_key);
String hash = Base64.getEncoder().encodeToString(sha256_HMAC.doFinal(message.getBytes()));
System.out.println(hash);
}
catch (Exception e){
System.out.println("Error");
}
**What to use instead of Base64.getEncoder().encode? **
I am trying to encrypt a plain text string to integrate with a third-party system using AES encryption. The receiver does not have any document to explain what their encryption algorithm is, and they've simply shared the below Java code to explain how the encryption works:
import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
public class CipherData {
private static final String SALT = "pa(MS"; //Salt. Required for key generation
private static final String ALGORITHM = "AES/CBC/PKCS5Padding"; //Encryption Algorithm/Cipher/Padding
private static String SECRET_KEY_FACTORY_ALGORITHM = "PBKDF2WithHmacSHA1";
private static int KEY_SIZE = 256;
private static final String TOKEN = "Pre123454sk"; //Password. Used for Key Generation
private static String initVector = "pre1234Init12345"; //IV. Required for Key generation
private static int KEY_ITERATIONS = 22123;
public static String encrypt(String value) throws Exception { //Encryption Module
Cipher cipher = Cipher.getInstance(ALGORITHM);
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
Key key = generateKey();
cipher.init(1, key, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
return base64Encode(encrypted);
}
public static String decrypt(String value) throws Exception { //Decryption module
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
Key key = generateKey();
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(2, key, iv);
byte[] original = cipher.doFinal(base64Decode(value));
return new String(original);
}
private static Key generateKey() throws Exception { //AES Key Generation
byte[] saltBytes = SALT.getBytes("UTF-8");
SecretKeyFactory skf = SecretKeyFactory.getInstance(SECRET_KEY_FACTORY_ALGORITHM);
PBEKeySpec spec = new PBEKeySpec(TOKEN.toCharArray(), saltBytes, KEY_ITERATIONS, KEY_SIZE);
SecretKey secretKey = skf.generateSecret(spec);
SecretKeySpec key = new SecretKeySpec(secretKey.getEncoded(), "AES");
return key;
}
private static String base64Encode(byte[] token) {
String encoded = DatatypeConverter.printBase64Binary(token);
return encoded;
}
private static byte[] base64Decode(String token) {
return DatatypeConverter.parseBase64Binary(token);
}
public static void main(String[] args) {
String clearPAN="ABCDE1234F", encrPAN="", decrPAN="";
try {
encrPAN=encrypt(clearPAN);
decrPAN=decrypt(encrPAN);
System.out.println("Clear PAN: " + clearPAN);
System.out.println("Encrypted PAN: " + encrPAN);
System.out.println("Decrypted PAN: " + decrPAN);
}
catch (Exception e) {
System.out.print("Exception Occured in main()");
e.printStackTrace();
}
}
}
I am developing my application in .NET, and I'm unable to get the same string as the receiver gets by using the above Java code, and we're out of ideas on what we should be doing.
Here is my .NET algorithm (I have just inferred this logic from the Java code, and this is my first time with Java, so please be kind if I've made a stupid error):
private static String TOKEN = "Pre123454sk";
private static String initVector = "pre1234Init12345";
private static string Encryption(string plainText)
{
using (var aesProvider = new AesCryptoServiceProvider())
{
//String SALT = "pa(MS";
PasswordDeriveBytes pdb = new PasswordDeriveBytes(TOKEN, Encoding.UTF8.GetBytes("pa(MS"));
pdb.IterationCount = 22123;
aesProvider.KeySize = 256;
aesProvider.Padding = PaddingMode.PKCS7;
aesProvider.Mode = CipherMode.CBC;
aesProvider.Key = pdb.GetBytes(16);
aesProvider.IV = Encoding.UTF8.GetBytes(initVector);
Byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
ICryptoTransform encryptor = aesProvider.CreateEncryptor(aesProvider.Key, aesProvider.IV);
using (var memStream = new MemoryStream())
{
using (var cryptoStream = new CryptoStream(memStream, encryptor, CryptoStreamMode.Write))
{
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
cryptoStream.FlushFinalBlock();
Byte[] cipherTextBytes = memStream.ToArray();
memStream.Close();
memStream.Flush();
cryptoStream.Close();
return Convert.ToBase64String(cipherTextBytes);
}
}
}
}
private static string Decryption(string plainText)
{
using (var aesProvider = new AesCryptoServiceProvider())
{
PasswordDeriveBytes pdb = new PasswordDeriveBytes(TOKEN, Encoding.UTF8.GetBytes("pa(MS"));
pdb.IterationCount = 22123;
aesProvider.KeySize = 256;
aesProvider.Padding = PaddingMode.Zeros;
aesProvider.Mode = CipherMode.CBC;
aesProvider.Key = pdb.GetBytes(16);
aesProvider.IV = Encoding.UTF8.GetBytes(initVector);
byte[] cipherTextBytes1 = Convert.FromBase64String(plainText);
ICryptoTransform decryptor = aesProvider.CreateDecryptor(aesProvider.Key, aesProvider.IV);
using (MemoryStream memoryStream = new MemoryStream(cipherTextBytes1))
{
using (CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, decryptor, CryptoStreamMode.Read))
{
using (StreamReader streamReader = new StreamReader((Stream)cryptoStream))
{
return streamReader.ReadToEnd();
}
}
}
}
}
I've already tried replacing the TOKEN value with the IV value in the PasswordDeriveBytes() function, but it's still giving me the same error (the receiving system is unable to decrypt this request).
Here is the plain text that I'm trying to encrypt: CXQPM4656P
Here is what I'm getting from the .NET code: pKjfaKu4AxBEbagiAWoLkg==
Here is what I should be getting: kY8lgWh97fqkm9gS8zgMHg==
I'm out of ideas at this point, and I have no support from the other end. Would be great if someone can help me figure it out.
Thanks to Topaco (first comment in the question), I was able to sort it out.
Here is Topaco's original answer:
The Java code uses PBKDF2 with HMAC/SHA1, the C# code an algorithm based on PBKDF1. For PBKDF2 in the C# code PasswordDeriveBytes has to be replaced by Rfc2898DeriveBytes (with HMAC/SHA1 as default). Note that the .NET implementation expects a minimum 8 bytes salt. Also, Java uses a 32 bytes key, the C# code a 16 bytes key. With consistent key derivation and key size, the generated ciphertexts are identical. –
Topaco
The only thing I had to change was to upgrade from .NET 5 to .NET 6.
I have one Perl Script that sign the payload with private key(id_rsa), generated in linux machine with SSH-Keygen or OpenSSL. After that I am taking the hash or the signed value, decoding it in base 64 and sending to my scala code. Here I send two things, the public key(id_rsa.pub) and the encoded signature.
Now when I verify the signature it always give me false result.
I have tried the opposite, like singing in Scala and verifying with Perl. It returned me false.
I generated the keys in Scala and put them in Linux and done the signing and verifying part from Perl. It worked. It even worked when I imported both keys in scala and try to do the signing and verifying part both from scala.
But whenever I am mixing up these two like signing in Perl and verifying in Scala or signing in Scala and verifying in Perl, it is giving me a false result.
My question is is there any common format that I can use here. The code I am using is -
val fileOpened = Source.fromFile("file.hash") // taking the hashed value
val payload = fileOpened.getLines.mkString //file contents as string
val decodedString = Base64.getDecoder.decode(payload.getBytes) // Base64 Decoding of the hashed value
println("decodedString, the hash value was base64 encoded, so decoded it and took into bytes")
println(decodedString) //the hash value was base64 encoded, so decoded it and took into bytes
val fileOpened1 = Source.fromFile("file")
val ComareElement = fileOpened1.getLines.mkString
val decodedString1 = new String(ComareElement).getBytes
println("decodedString1, this is the main payload to compare, turned into bytes")
println(decodedString1) // this is the main payload to compare, turned into bytes
//val keyPairGen = KeyPairGenerator.getInstance("RSA") //Creating KeyPair generator object
//keyPairGen.initialize(2048, new SecureRandom) //Initializing the key pair generator
//val pair: KeyPair = keyPairGen.generateKeyPair
// save public key as id_rsa.pub
//val x509keySpec = new X509EncodedKeySpec(pair.getPublic.getEncoded)
//val publicKeyStream = new FileOutputStream("C:\\MyWorkSpace\\PayLoadSign\\src\\main\\scala\\id_rsa.pub")
//publicKeyStream.write(x509keySpec.getEncoded)
// save private key as id_rsa
//val pkcs8KeySpec = new PKCS8EncodedKeySpec(pair.getPrivate.getEncoded)
//val privateKeyStream = new FileOutputStream("id_rsa")
//privateKeyStream.write(pkcs8KeySpec.getEncoded)
val filePublicKey = new File("id_rsa.pub")
var inputStream = new FileInputStream("id_rsa.pub")
val encodedPublicKey: Array[Byte] = new Array[Byte](filePublicKey.length.toInt)
inputStream.read(encodedPublicKey)
inputStream.close()
val filePrivateKey = new File("id_rsa")
inputStream = new FileInputStream("id_rsa")
val encodedPrivateKey: Array[Byte] = new Array[Byte](filePrivateKey.length.toInt)
println("The key is now " +encodedPrivateKey)
inputStream.read(encodedPrivateKey)
inputStream.close()
val keyFactory: KeyFactory = KeyFactory.getInstance("RSA")
// public key
val publicKeySpec: X509EncodedKeySpec = new X509EncodedKeySpec(encodedPublicKey)
val publicKey: PublicKey = keyFactory.generatePublic(publicKeySpec)
// private key
val privateKeySpec: PKCS8EncodedKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey)
val privateKey: PrivateKey = keyFactory.generatePrivate(privateKeySpec)
new KeyPair(publicKey, privateKey)
// val pair = keyPairGen.generateKeyPair //Generate the pair of keys
//val privKey = pair.getPrivate //Getting the privatekey from the key pair
//val pubKey = pair.getPublic //Getting the PublicKey from the key pair
val privKey = privateKey //Getting the privatekey from the key pair
val pubKey = publicKey //Getting the PublicKey from the key pair
println("Getting the privateKey from the key pair " + privateKey)
println("Getting the publicKey from the key pair " + publicKey)
var writer = new PrintWriter(new File("C:\\MyWorkSpace\\PayLoadSign\\src\\main\\scala\\Private_Key"))
writer.write(privKey.toString)
writer.close()
writer = new PrintWriter(new File("C:\\MyWorkSpace\\PayLoadSign\\src\\main\\scala\\Public_Key"))
writer.write(pubKey.toString)
writer.close()
val sign = Signature.getInstance("SHA256withRSA") //Creating a Signature object
//sign.initSign(privKey)
//val bytes = payload.getBytes //Initializing the signature
val bytes = decodedString
println(bytes)
//sign.update(bytes) //Adding data to the signature
//val signature = sign.sign //Calculating the signature
//val signedPayload = new BASE64Encoder().encode(signature)
//writer = new PrintWriter(new File("file.hash"))
//writer.write(signedPayload)
//writer.close()
println(bytes)
sign.initVerify(pubKey) //Initializing the signature
sign.update(bytes)
//println(signature)
//val bool = sign.verify(new BASE64Decoder().decodeBuffer(signedPayload)) //Verifying the signature
//println(sign.verify(signature))
val bool = sign.verify(bytes)
println(bool)
if (bool) System.out.println("Signature verified")
else System.out.println("Signature failed")
}
I have found a solution. I achieved that by correcting the signature algorithm and its default values in Perl. Also you have to take care of the new line characters (\n). The modified code for verification should look like this in Scala -
object Verify_Test extends App {
var privateKeyString = new String(Files.readAllBytes(Paths.get("private_key.pem")), Charset.defaultCharset)
privateKeyString = privateKeyString.replaceAll("\\n", "").replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "")
var publicKeyString = new String(Files.readAllBytes(Paths.get("public_key.pem")), Charset.defaultCharset)
publicKeyString = publicKeyString.replaceAll("\\n", "").replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "")
val keyFactory = KeyFactory.getInstance("RSA")
val encodedPrivateKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyString))
val privateKey = keyFactory.generatePrivate(encodedPrivateKeySpec)
val encodedPublicKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyString))
val publicKey = keyFactory.generatePublic(encodedPublicKeySpec)
var encodedSignatureVale = new String(Files.readAllBytes(Paths.get("hashedSignature")), Charset.defaultCharset)
encodedSignatureVale = encodedSignatureVale.replaceAll("\\n", "")
println(encodedSignatureVale)
var actualValue = new String(Files.readAllBytes(Paths.get("original data")), Charset.defaultCharset)
//actualValue = actualValue.replaceAll("\\n", "")
println(actualValue)
val signature = Signature.getInstance("SHA256withRSA")
//signature.initSign(privateKey)
//signature.update("Hello, World\n".getBytes("UTF-8"))
//val signatureValue = signature.sign
//val encodedSignatureVale = Base64.encodeBase64String(signatureValue)
//println(Base64.encodeBase64String(signatureValue))
signature.initVerify(publicKey)
// signature.update("Hello, World\n".getBytes("UTF-8"))
signature.update(actualValue.getBytes("UTF-8"))
val bool = signature.verify(Base64.decodeBase64(encodedSignatureVale))
println(bool)
if (bool) println("Signature verified")
else println("Signature failed")}
Remember I have taken both private and public key here. But we can use only public for verification.
The Perl code for signing will be -
use File::Slurp qw(read_file);
use File::Slurp qw(write_file);
use MIME::Base64 qw(encode_base64);
require Crypt::PK::RSA;
my $datatosign = read_file( 'payload.txt');
my $privatekey = Crypt::PK::RSA->new('private_key.pem');
my $signature = $privatekey->rsa_sign_message($datatosign, 'SHA256', 'v1.5');
my $hash = encode_base64($signature, '');
write_file('payload.hash', $hash);
I am working on translating an API from Java into Javascript (nodeJs). The problem is that the signatures generated by the Java code are much shorter than the one in javascript. The results from the getSignature function have different length and as such whenever I generate a signature in javascript the server won't recognize it but it will when it is generated in Java.
I have verified that the values in getSignatureKey are the same in both functions and the getSignature function uses the output from getSignatureKey to encrypt "SOME MESSAGE TO ENCRYPT" which will be the request body in plain text (verified both have the same content and format).
Is there any reason why the output differs in length? Perhaps some encoding problem or something else I'm not seeing.
Using the native crypto library in nodeJs as follows:
var getSignatureKey = function(key, api_key, dateStamp){
let kUser = HMACSHA256("CAS"+api_key, key);
let kDate = HMACSHA256(dateStamp, kUser);
let kService = HMACSHA256(SERVICE_NAME, kDate);
let kSigning = HMACSHA256("cas_request", kService);
return kSigning;
}
var getSignature = function(signature_key){
let signature_bytes = HMACSHA256("SOME MESSAGE TO ENCRYPT", signature_key);
let signature = Buffer.from(signature_bytes).toString('base64');
return signature;
}
var HMACSHA256 = function(message, secret){
let key_bytes = encoder.encode(secret);
let message_bytes = encoder.encode(message);
let hash = crypto.createHmac('sha256', key_bytes).update(message_bytes).digest();
return Uint8Array.from(hash);
}
While in java I have the following code:
public static byte[] getSignatureKey(String key, String apiKey, String dateStamp, String serviceName)
throws Exception {
byte[] kSecret = key.getBytes("UTF8");
byte[] kUser = HmacSHA256("CAS" + apiKey, kSecret);
byte[] kDate = HmacSHA256(dateStamp, kUser);
byte[] kService = HmacSHA256(serviceName, kDate);
byte[] kSigning = HmacSHA256("cas_request", kService);
return kSigning;
}
public static String getSignature(byte[] signature_key) throws Exception {
return Base64.encodeBase64String(HmacSHA256("SOME MESSAGE TO ENCRYPT", signature_key));
}
public static byte[] HmacSHA256(String data, byte[] key) throws Exception {
String algorithm = "HmacSHA256";
Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(key, algorithm));
return mac.doFinal(data.getBytes("UTF8"));
}
I have the following code to extract Private Key
PEMParser parser = new PEMParser(new InputStreamReader(new ByteArrayInputStream(decoded)));
Object object = parser.readObject();
PEMDecryptorProvider provider = new JcePEMDecryptorProviderBuilder()
.build(props.getProperty(KeytoolFlags.KEYPASS.name()).toCharArray());
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME);
if (object instanceof PEMEncryptedKeyPair) {
KeyPair pair = converter.getKeyPair(((PEMEncryptedKeyPair) object).decryptKeyPair(provider));
return loadPublic ? pair.getPublic() : pair.getPrivate();
} else if (object instanceof PEMKeyPair) {
return loadPublic ? converter.getPublicKey(((PEMKeyPair) (object)).getPublicKeyInfo())
: converter.getPrivateKey(((PEMKeyPair) (object)).getPrivateKeyInfo());
} else {
InputDecryptorProvider p2 = new JceOpenSSLPKCS8DecryptorProviderBuilder()
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
.build(props.getProperty(KeytoolFlags.KEYPASS.name()).toCharArray());
return converter.getPrivateKey(((PKCS8EncryptedPrivateKeyInfo) object).decryptPrivateKeyInfo(p2));
}
I would like to get the Public Key from converter when it's JceOpenSSLPKCS8DecryptorProviderBuilder. Is there any way?
Thanks,
The simplest way, although it feels rather ugly to me, is to convert the private key 'back' to one of OpenSSL's 'legacy' forms, which PEMParser is then able to turn into a PEMKeyPair with both halves, from which the public can be selected. Otherwise, the method must be tailored depending on the key algorithm aka type, but can be more efficient which I like better. Here are both options for your consideration:
public static void SO57043669PKCS8_Public_BC (String[] args) throws Exception {
Object p8e = new PEMParser (new FileReader (args[0])).readObject();
// for PKCS8-encrypted result is org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo
PrivateKeyInfo p8i = ((PKCS8EncryptedPrivateKeyInfo)p8e).decryptPrivateKeyInfo(
new JceOpenSSLPKCS8DecryptorProviderBuilder().build(args[1].toCharArray()) );
// or get org.bouncycastle.asn1.pkcs.PrivateKeyInfo directly from PEMParser for PKCS8-clear
PublicKey pub = null;
if( args.length>=3 ){ // the simple way:
PrivateKey prv = new JcaPEMKeyConverter().getPrivateKey(p8i);
PemObject old = new JcaMiscPEMGenerator (prv,null).generate();
StringWriter w1 = new StringWriter();
PemWriter w2 = new PemWriter(w1);
w2.writeObject(old); w2.close();
Object pair = new PEMParser(new StringReader(w1.toString())).readObject();
pub = new JcaPEMKeyConverter().getKeyPair((PEMKeyPair)pair).getPublic();
}else{
ASN1ObjectIdentifier id = p8i.getPrivateKeyAlgorithm().getAlgorithm();
PKCS8EncodedKeySpec p8s = new PKCS8EncodedKeySpec (p8i.getEncoded());
if( id.equals(PKCSObjectIdentifiers.rsaEncryption) ){
// the standard PKCS1 private key format for RSA redundantly includes e
KeyFactory rfact = KeyFactory.getInstance("RSA");
RSAPrivateCrtKey rprv = (RSAPrivateCrtKey) rfact.generatePrivate(p8s);
// or JcaPEMKeyConverter.getPrivateKey does the same thing
pub = /*(RSAPublicKey)*/ rfact.generatePublic(
new RSAPublicKeySpec (rprv.getModulus(), rprv.getPublicExponent()));
}else if( id.equals(X9ObjectIdentifiers.id_dsa) ){
// the apparently ad-hoc format OpenSSL uses for DSA does not include y but it can be computed
KeyFactory dfact = KeyFactory.getInstance("DSA");
DSAPrivateKey dprv = (DSAPrivateKey) dfact.generatePrivate(p8s);
// or JcaPEMKeyConverter.getPrivateKey does the same thing
BigInteger p = dprv.getParams().getP(), q = dprv.getParams().getQ(), g = dprv.getParams().getG();
pub = /*(DSAPublicKey)*/ dfact.generatePublic (
new DSAPublicKeySpec(g.modPow(dprv.getX(),p), p, q, g) );
// warning: naive computation probably vulnerable to sidechannel attack if any
}else if( id.equals(X9ObjectIdentifiers.id_ecPublicKey) ){
// the SECG SEC1 format for EC private key _in PKCS8 by OpenSSL_
// includes []] BITSTR(Q) (but not [0] params which is already in the PKCS8 algid)
org.bouncycastle.asn1.sec.ECPrivateKey eprv = org.bouncycastle.asn1.sec.ECPrivateKey.getInstance(p8i.parsePrivateKey());
byte[] eenc = new SubjectPublicKeyInfo (p8i.getPrivateKeyAlgorithm(), eprv.getPublicKey().getOctets()).getEncoded();
KeyFactory efact = KeyFactory.getInstance("EC");
pub = /*(ECPublicKey)*/ KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(eenc));
//}else if maybe others ...
}else throw new Exception ("unknown private key OID " + id);
}
System.out.println (pub.getAlgorithm() + " " + pub.getClass().getName());
}
In addition to the other answer, here's a way to convert Ed25519 private keys to public keys.
Edit: As #tytk notes in the comments, given a BCEdDSAPrivateKey, you can just call getPublicKey(). I am not entirely sure if it's possible to obtain a private EdDSAKey that wouldn't be BCEdDSAPrivateKey, using BC or otherwise, but just in case I'm leaving an alternative codepath that works.
private val bouncyCastleProvider = BouncyCastleProvider()
private val pkcs8pemKeyConverter = JcaPEMKeyConverter().setProvider(bouncyCastleProvider)
fun makeKeyPair(keyReader: Reader, passphrase: CharArray): KeyPair {
var obj = PEMParser(keyReader).readObject()
...
if (obj is PrivateKeyInfo) {
val privateKey = pkcs8pemKeyConverter.getPrivateKey(obj)
when (privateKey) {
is BCEdDSAPrivateKey -> return KeyPair(privateKey.publicKey, privateKey)
is EdDSAKey -> return KeyPair(genEd25519publicKey(privateKey, obj), privateKey)
}
...
}
...
}
private fun genEd25519publicKey(privateKey: EdDSAKey, privateKeyInfo: PrivateKeyInfo): PublicKey {
val privateKeyRaw = ASN1OctetString.getInstance(privateKeyInfo.parsePrivateKey()).octets
val privateKeyParameters = Ed25519PrivateKeyParameters(privateKeyRaw)
val publicKeyParameters = privateKeyParameters.generatePublicKey()
val spi = SubjectPublicKeyInfo(privateKeyInfo.privateKeyAlgorithm, publicKeyParameters.encoded)
val factory = KeyFactory.getInstance(privateKey.algorithm, bouncyCastleProvider)
return factory.generatePublic(X509EncodedKeySpec(spi.encoded))
}