I am trying to encrypt a string in ruby using Cipher with AES algorithm. I have example written in Java. I have taken help from this example and written code in Java but not able to get the same output as in JAVA.
Following is the code written in java
import java.util.Base64;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.Key;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import java.util.Arrays;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class HelloWorld {
public static final String PHONENUMBER_PARAM = "phoneNumber";
public static final String PIN_PARAM ="pin";
public static final String MERCHANTID_PARAM = "merchantId";
public static void main(String args[]) throws Exception {
String phoneNumber ="+917738995286";
String pin ="5577";
String merchantId ="527425858";
String encodedKey ="vPDkdTDrcygLPROzd1829A==";
String payLoad = PHONENUMBER_PARAM + "=" + URLEncoder.encode(phoneNumber, "UTF-8")+ "&" + PIN_PARAM + "=" + URLEncoder.encode(pin, "UTF-8") ;
byte[] decodedKey = Base64.getDecoder().decode(encodedKey.getBytes());
Key encryptionKey = new SecretKeySpec(decodedKey, "AES");
byte[] utf8Bytes = payLoad.getBytes("utf-8");
byte[] encryptedBody = encrypt(encryptionKey, utf8Bytes);
String encryptedData = new String(Base64.getEncoder().encode(encryptedBody));
System.out.println("encryptedData:" + encryptedData);
}
private static byte[] encrypt(Key encryptionKey, byte[] data) throws Exception {
Cipher c = Cipher.getInstance("AES");
c.init(1, encryptionKey);
return c.doFinal(data);
}
}
Output of this code is
encryptedData:lE40HlECbxU/mWRivF/+Szm3PprMoLW+Y7x911GczunakbG8l+A2JVEEP8gTw6xy
I tried to write the same code in ruby. Ruby Code is:
payLoad = "phoneNumber=%2B917738995286&pin=5577"
encodedKey = "vPDkdTDrcygLPROzd1829A=="
decodedKey = Base64.decode64(encodedKey)
dKey = decodedKey.each_byte.map { |b| b.to_s(16) }.join
cipher = OpenSSL::Cipher.new('aes128').encrypt
encryptionKey = cipher.update(dKey)
encryptionKey<< cipher.final
utf8Bytes = payLoad.bytes
uKey = utf8Bytes.map { |b| b.to_s(16) }.join
scipher = OpenSSL::Cipher.new('aes128').encrypt
scipher.key = encryptionKey
encryptedBody = scipher.update(uKey)
encryptedBody<< scipher.final
encryptedData = Base64.encode64(encryptedBody)
Output of this code is
CqFmCKJ004PsoXi2tDCTBmx7/iTHVyDsFH9y8NWNrEP3k3bOQp7h8uyl/a7Z\nYi9ZmcXSspo6FCyCo6fJIwPohg==\n
Don't know where is the error. I have already worked for 2 days but not able to get any answer. Any help will be great. Thanks in advance.
The following version outputs the same result as your Java code:
# crypt.rb
require 'base64'
require 'openssl'
payLoad = "phoneNumber=%2B917738995286&pin=5577"
encodedKey = "vPDkdTDrcygLPROzd1829A=="
decodedKey = Base64.decode64(encodedKey)
scipher = OpenSSL::Cipher.new('aes-128-ecb').encrypt
scipher.key = decodedKey
encryptedBody = scipher.update(payLoad)
encryptedBody << scipher.final
encryptedData = Base64.encode64(encryptedBody)
puts encryptedData
$ ruby crypt.rb
# => lE40HlECbxU/mWRivF/+Szm3PprMoLW+Y7x911GczunakbG8l+A2JVEEP8gT
# w6xy
Notable differences from your ruby script version:
You must specify the cipher mode. The problem is that Java defaults to the ECB mode whereas ruby defaults to the CBC mode. By the way, the ECB mode is considered less-secure today and you actually should not use it.
In ruby, you tried to re-encrypt the Base64-decoded version of your encryption key, which is something you don't do in the Java version.
In ruby, there is no need to do the string to byte conversions, you can encrypt strings right away.
So, although the two scripts output the same encrypted data now, I would strongly consider changing the AES mode of operation, to actually stay secure.
Related
I have written a code in beanshell for encryption and decryption in Jmeter but somehow its not working. I am getting error : In file: inline evaluation of: ``import java.security.InvalidAlgorithmParameterException; import java.security.In . . . '' Encountered "}" at line 19, column 82.
I have added the crypto jar in test plan but issue persists.
Attached is the code.
Working :
I am generating a random string(R1) and encryption it with a public key using RSA algo.
Using the key R1, I need to encrypt the request body using AES algo, CBC mode, PKCS7Padding.
package com.sample.feedbackrating;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.UUID;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class Crypto {
public static IvParameterSpec generateIv() {
byte[] iv = new byte[16];
return new IvParameterSpec(iv);
}
public static String encrypt(String algorithm, String input, String secretKey, IvParameterSpec iv) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException
{
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, new
SecretKeySpec(secretKey.getBytes(), "AES"), iv);
byte[] cipherText = cipher.doFinal(input.getBytes());
return Base64.getEncoder().encodeToString(cipherText);
}
public static String decrypt(String algorithm, String cipherText, String secretKey, IvParameterSpec iv) throws NoSuchPaddingException, NoSuchAlgorithmException,InvalidAlgorithmParameterException, InvalidKeyException,BadPaddingException, IllegalBlockSizeException
{
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(secretKey.getBytes(), "AES"), iv);
byte[] plainText = cipher.doFinal(Base64.getDecoder().decode(cipherText));
return new String(plainText);
}
public static String generateSecretKey() {
return UUID.randomUUID().toString().replace("-", "");
}
}
Encountered "}" is a syntax error so double check your Beanshell script for eventual missing semicolons or closing brackets or whatever. You can use online lint tool like this one to see where exactly the problem is
In general I cannot reproduce your issue:
So it's either a copy-paste issue or a Beanshell-related problem, be aware that starting from JMeter 3.1 you're supposed to use JSR223 Test Elements and Groovy language for scripting, especially for "heavy" tasks like cryptographic operations.
As can be seen from the screenshot(https://jwt.io/) which is the expected signature, I am getting wrong java code. I am missing something very obvious wrong with code?
package com.company;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class Main {
public static void main(String[] args) throws InvalidKeyException, NoSuchAlgorithmException {
final Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec("qwertyuiopasdfghjklzxcvbnm123456".getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
String sourcString = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2MjM5NTk5NDEsImV4cCI6MTY1NTQ5NTk0MSwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6Impyb2NrZXRAZXhhbXBsZS5jb20ifQ";
byte[] signatureBytes = mac.doFinal(sourcString.getBytes(StandardCharsets.UTF_8));
System.out.println("Signatured=" + Base64.getEncoder().withoutPadding().encodeToString(signatureBytes));
}
}
OUTPUT : Signatured=ZFrcK6AZzYCTs0ugepzcSFMxxuY5Fs0PtMXGDZtT3sA
http://tpcg.io/P0MtSDC3
Looks like you almost got it, since you provide the SecretKeySpec in clear text you don't have to check this check box ... or provide a clear text SecretKey that actually encodes to "qwertyuiopasdfghjklzxcvbnm123456".
Here is the code, the second (message, signature and public key) works fine on Java, can verify the message. But when I am using python, it will failed.
If am signed the message and the code will verify the message correctly.
Would some one help me to check the problem? Thank you.
# -*- coding:utf-8 -*-
from Crypto.PublicKey import RSA
from Crypto.Hash import SHA256
from Crypto.Signature import PKCS1_v1_5
from base64 import b64decode, b64encode
public_key = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALyJy3rlD9EtWqVBzSIYxRRuFWRVn3juht2nupDCBSsWi7uKaRu3W0gn5y6aCacArtCkrf0EehwYRm0A4iHf8rkCAwEAAQ=='
private_key = 'MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAvInLeuUP0S1apUHNIhjFFG4VZFWfeO6G3ae6kMIFKxaLu4ppG7dbSCfnLpoJpwCu0KSt/QR6HBhGbQDiId/yuQIDAQABAkEAqm/y15UtOE7Ey/HxLCqyNqbRhdN1h5AxsT0IhgYvP+PhWGc3hRElMwNCdiNaJBh04R1iK6wmKoi3DSjkdU6IAQIhAPRL9khAdPMxjy5tpswNWeaDjNJrlUKEnItQUkoHqve5AiEAxZIDz235HcUgLg9ApYK4spOpzLDGCCgfO3FxmrUEUwECIEaLjQIOQvdbT1p75Ze1H0nWoRq+YGrF+qKsPicMkc1ZAiARlNTR+K9afthGQQU3tVJKUemiVXjJ8QgWehnp8oHYAQIhANsC2fEVjWv94Oy2c8I9qhuX+yfNtvZ2m+Kmf2o4JFrR'
bank_response_data = """{"head":{"vernbr":"1.0","mchnbr":"BILL0003","mchtyp":"BILLTYP","trscod":"BILL001","msgidc":"201805011230500001","sigtim":"20190307115511","sigalg":"SHA256WithRSA","retcod":"F","retmsg":"GWB2B006 源IP地址不在商户IP白名单中;商户编号:BILL0003;IP地址:123.139.40.150"}}"""
bank_response_signature = """uZl0/5D694GnAd/G9OPRs9BSd9fb0fZGXSGThBtgLnKi+CDQAdasOX05mKazXZki0blXxApGYRAWa/kOrf+Wl0USfklx0G5w/eGERfMdRWpvtV3S2MBCH/H/0T81nKGgn8svkT/Trj7+Mc+e654Jn8IijGyV9m8Ak92hG2bLtbc="""
bank_public_key = """MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDZs4l8Ez3F4MG0kF7RRSL+pn8MmxVE3nfdXzjx6d3rH8IfDbNvNRLS0X0b5iJnPyFO8sbbUo1Im4zX0M8XA0xnnviGyn5E6occiyUXJRgokphWb5BwaYdVhnLldctdimHoJTk3NFEQFav3guygR54i3tymrDc8lWtuG8EczVu8FwIDAQAB"""
def sign():
key_bytes = bytes(private_key, encoding="utf-8")
key_bytes = b64decode(key_bytes)
key = RSA.importKey(key_bytes)
hash_value = SHA256.new(bytes(bank_response_data, encoding="utf-8"))
signer = PKCS1_v1_5.new(key)
signature = signer.sign(hash_value)
return b64encode(signature)
def verify(data, signature, the_pub_key):
print(signature)
key_bytes = bytes(the_pub_key, encoding="utf-8")
key_bytes = b64decode(key_bytes)
key = RSA.importKey(key_bytes)
hash_value = SHA256.new(bytes(data, encoding="utf-8"))
verifier = PKCS1_v1_5.new(key)
if verifier.verify(hash_value, b64decode(signature)):
print("The signature is authentic.")
else:
print("The signature is not authentic.")
verify(bank_response_data, sign(), public_key)
verify(bank_response_data, bank_response_signature.encode('utf-8'),
bank_public_key)
# -*- coding:utf-8 -*-
from Cryptodome.Signature import PKCS1_v1_5 # pip install pycryptodomex
from Cryptodome.Hash import SHA256
from Cryptodome.PublicKey import RSA
from base64 import decodebytes, encodebytes
public_key = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALyJy3rlD9EtWqVBzSIYxRRuFWRVn3juht2nupDCBSsWi7uKaRu3W0gn5y6aCacArtCkrf0EehwYRm0A4iHf8rkCAwEAAQ=="
private_key = "MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAvInLeuUP0S1apUHNIhjFFG4VZFWfeO6G3ae6kMIFKxaLu4ppG7dbSCfnLpoJpwCu0KSt/QR6HBhGbQDiId/yuQIDAQABAkEAqm/y15UtOE7Ey/HxLCqyNqbRhdN1h5AxsT0IhgYvP+PhWGc3hRElMwNCdiNaJBh04R1iK6wmKoi3DSjkdU6IAQIhAPRL9khAdPMxjy5tpswNWeaDjNJrlUKEnItQUkoHqve5AiEAxZIDz235HcUgLg9ApYK4spOpzLDGCCgfO3FxmrUEUwECIEaLjQIOQvdbT1p75Ze1H0nWoRq+YGrF+qKsPicMkc1ZAiARlNTR+K9afthGQQU3tVJKUemiVXjJ8QgWehnp8oHYAQIhANsC2fEVjWv94Oy2c8I9qhuX+yfNtvZ2m+Kmf2o4JFrR"
bank_response_data = """{"head":{"vernbr":"1.0","mchnbr":"BILL0003","mchtyp":"BILLTYP","trscod":"BILL001","msgidc":"201805011230500001","sigtim":"20190307115511","sigalg":"SHA256WithRSA","retcod":"F","retmsg":"GWB2B006 源IP地址不在商户IP白名单中;商户编号:BILL0003;IP地址:123.139.40.150"}}"""
bank_response_signature = """uZl0/5D694GnAd/G9OPRs9BSd9fb0fZGXSGThBtgLnKi+CDQAdasOX05mKazXZki0blXxApGYRAWa/kOrf+Wl0USfklx0G5w/eGERfMdRWpvtV3S2MBCH/H/0T81nKGgn8svkT/Trj7+Mc+e654Jn8IijGyV9m8Ak92hG2bLtbc="""
bank_public_key = """MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDZs4l8Ez3F4MG0kF7RRSL+pn8MmxVE3nfdXzjx6d3rH8IfDbNvNRLS0X0b5iJnPyFO8sbbUo1Im4zX0M8XA0xnnviGyn5E6occiyUXJRgokphWb5BwaYdVhnLldctdimHoJTk3NFEQFav3guygR54i3tymrDc8lWtuG8EczVu8FwIDAQAB"""
def sign(private_key=private_key, raw_string=bank_response_data):
private_key = RSA.importKey(decodebytes(private_key.encode()))
signer = PKCS1_v1_5.new(private_key)
signature = signer.sign(SHA256.new(raw_string.encode()))
return encodebytes(signature).decode().replace("\n", "")
def verify(data, signature, public_key):
print(signature)
key = RSA.importKey(decodebytes(public_key.encode()))
hash_value = SHA256.new(data.encode())
verifier = PKCS1_v1_5.new(key)
if verifier.verify(hash_value, decodebytes(signature.encode())):
print("The signature is authentic.")
else:
print("The signature is not authentic.")
verify(bank_response_data, sign(), public_key)
verify(bank_response_data, bank_response_signature, bank_public_key)
Below is the java code, it works fine with the same signature and public key.
import org.apache.commons.codec.digest.DigestUtils;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class MyTest {
public static final String KEY_ALGORITHM = "RSA";
public static final String SIGNATURE_ALGORITHM = "SHA256WithRSA";
public static void main(String[] args) {
String data = "{\"head\":{\"vernbr\":\"1.0\",\"mchnbr\":\"BILL0003\",\"mchtyp\":\"BILLTYP\",\"trscod\":\"BILL001\",\"msgidc\":\"201805011230500001\",\"sigtim\":\"20190307115511\",\"sigalg\":\"SHA256WithRSA\",\"retcod\":\"F\",\"retmsg\":\"GWB2B006 源IP地址不在商户IP白名单中;商户编号:BILL0003;IP地址:123.139.40.150\"}}";
String signature = "dnAFU2e5zFb8rJ1mXDNk5AG9UyujVIUArkBjb1Nonf7iMhZwHfHRO633eW5n7uELFnyJZk6Go2D6ovp4jEnIoA==";
String cmbcRespSignature = "uZl0/5D694GnAd/G9OPRs9BSd9fb0fZGXSGThBtgLnKi+CDQAdasOX05mKazXZki0blXxApGYRAWa/kOrf+Wl0USfklx0G5w/eGERfMdRWpvtV3S2MBCH/H/0T81nKGgn8svkT/Trj7+Mc+e654Jn8IijGyV9m8Ak92hG2bLtbc=";
String cmbcPublicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDZs4l8Ez3F4MG0kF7RRSL+pn8MmxVE3nfdXzjx6d3rH8IfDbNvNRLS0X0b5iJnPyFO8sbbUo1Im4zX0M8XA0xnnviGyn5E6occiyUXJRgokphWb5BwaYdVhnLldctdimHoJTk3NFEQFav3guygR54i3tymrDc8lWtuG8EczVu8FwIDAQAB";
try {
boolean verify = verify(DigestUtils.sha256(data), cmbcPublicKey, cmbcRespSignature);
if (verify) {
}
} catch (Exception e) {
}
}
public static boolean verify(byte[] data, String publicKey, String sign) throws Exception {
final Base64.Decoder decoder = Base64.getDecoder();
final byte[] keyBytes = publicKey.getBytes("UTF-8");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decoder.decode(keyBytes));
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PublicKey pubKey = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initVerify(pubKey);
signature.update(data);
return signature.verify(decoder.decode(sign));
}
}
Finally, I get the answer.
In java
DigestUtils.sha256(data) did hash,
SHA256WithRSA algorithm will do hash
So what I should do is hash 2 times in my python code
The latest (beta) version of Bouncy Castle (bcprov-jdk15on-161b20.jar) supports ED25519 and ED448 EC cryptography for signing purposes. I setup this full working example and it works as expected.
My question: did I rebuild the private and public keys correctly as I didn't found any example in the bc-tests ? I expected that I have to use some Spec-functionality like "X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(content)" for RSA-key rebuilding but my code is working.
package bc;
// original source: https://github.com/bcgit/bc-java/blob/master/core/src/test/java/org/bouncycastle/crypto/test/Ed25519Test.java
// needs bouncy castle beta: bcprov-jdk15on-161b20.jar (version 1.605)
// tested with Java 8 Build 191 x64
// this is a full working example for generating, signing, verififying with ed25519 keys
// code: https://github.com/java-crypto/Bouncy-Castle
import java.security.Provider;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Arrays;
import java.io.UnsupportedEncodingException;
import javax.xml.bind.DatatypeConverter;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.Signer;
import org.bouncycastle.crypto.generators.Ed25519KeyPairGenerator;
import org.bouncycastle.crypto.params.Ed25519KeyGenerationParameters;
import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
import org.bouncycastle.crypto.signers.Ed25519Signer;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class Ed25519Bc {
public static void main(String[] args) throws DataLengthException, CryptoException, UnsupportedEncodingException {
System.out.println("ED25519 with BC");
Security.addProvider(new BouncyCastleProvider());
Provider provider = Security.getProvider("BC");
System.out.println("Provider :" + provider.getName() + " Version: " + provider.getVersion());
// generate ed25519 keys
SecureRandom RANDOM = new SecureRandom();
Ed25519KeyPairGenerator keyPairGenerator = new Ed25519KeyPairGenerator();
keyPairGenerator.init(new Ed25519KeyGenerationParameters(RANDOM));
AsymmetricCipherKeyPair asymmetricCipherKeyPair = keyPairGenerator.generateKeyPair();
Ed25519PrivateKeyParameters privateKey = (Ed25519PrivateKeyParameters) asymmetricCipherKeyPair.getPrivate();
Ed25519PublicKeyParameters publicKey = (Ed25519PublicKeyParameters) asymmetricCipherKeyPair.getPublic();
// the message
byte[] message = "Message to sign".getBytes("utf-8");
// create the signature
Signer signer = new Ed25519Signer();
signer.init(true, privateKey);
signer.update(message, 0, message.length);
byte[] signature = signer.generateSignature();
// verify the signature
Signer verifier = new Ed25519Signer();
verifier.init(false, publicKey);
verifier.update(message, 0, message.length);
boolean shouldVerify = verifier.verifySignature(signature);
// output
byte[] privateKeyEncoded = privateKey.getEncoded();
byte[] publicKeyEncoded = publicKey.getEncoded();
System.out.println("privateKey Length :" + privateKeyEncoded.length + " Data:"
+ DatatypeConverter.printHexBinary(privateKeyEncoded));
System.out.println("publicKey Length :" + publicKeyEncoded.length + " Data:"
+ DatatypeConverter.printHexBinary(publicKeyEncoded));
System.out.println(
"signature Length :" + signature.length + " Data:" + DatatypeConverter.printHexBinary(signature));
System.out.println("signature correct :" + shouldVerify);
// rebuild the keys
System.out.println("Rebuild the keys and verify the signature with rebuild public key");
Ed25519PrivateKeyParameters privateKeyRebuild = new Ed25519PrivateKeyParameters(privateKeyEncoded, 0);
Ed25519PublicKeyParameters publicKeyRebuild = new Ed25519PublicKeyParameters(publicKeyEncoded, 0);
byte[] privateKeyRebuildEncoded = privateKeyRebuild.getEncoded();
System.out.println("privateKey Length :" + privateKeyRebuild.getEncoded().length + " Data:"
+ DatatypeConverter.printHexBinary(privateKeyRebuild.getEncoded()));
byte[] publicKeyRebuildEncoded = publicKeyRebuild.getEncoded();
System.out.println("publicKey Length :" + publicKeyRebuild.getEncoded().length + " Data:"
+ DatatypeConverter.printHexBinary(publicKeyRebuild.getEncoded()));
// compare the keys
System.out.println("private Keys Equal:" + Arrays.equals(privateKeyEncoded, privateKeyRebuildEncoded));
System.out.println("public Keys Equal :" + Arrays.equals(publicKeyEncoded, publicKeyRebuildEncoded));
// verify the signature with rebuild public key
Signer verifierRebuild = new Ed25519Signer();
verifierRebuild.init(false, publicKeyRebuild);
verifierRebuild.update(message, 0, message.length);
boolean shouldVerifyRebuild = verifierRebuild.verifySignature(signature);
System.out.println("signature correct :" + shouldVerifyRebuild + " with rebuild public key");
}
}
This is the console output that shows the properly rebuild keys:
ED25519 with BC
Provider :BC Version: 1.605
privateKey Length :32 Data:F6A1F3A0B8F44EE64ACE636AFCA262F656160A728C042E3F98F9A0FD45717DE7
publicKey Length :32 Data:858C2D6D5910B8AA7B52F7DF8E5806DAD3A7E43DC19C5A548F241BD8B82510FE
signature Length :64 Data:4D402B0095F6692742DCACB0C2C39BFB70A5687F162DFAB3721A660D2259C96B972DF41B97502347E534FAD8D59496811CDFFFA831264ECBB1429439CF350E08
signature correct :true
Rebuild the keys and verify the signature with rebuild public key
privateKey Length :32 Data:F6A1F3A0B8F44EE64ACE636AFCA262F656160A728C042E3F98F9A0FD45717DE7
publicKey Length :32 Data:858C2D6D5910B8AA7B52F7DF8E5806DAD3A7E43DC19C5A548F241BD8B82510FE
private Keys Equal:true
public Keys Equal :true
signature correct :true with rebuild public key
I'm also doing some research into Ed25519 so I've been looking at the BouncyCastle implementation along with Tink and libsodium; I certainly can't see anything wrong with how you're rebuilding the key pair, it seems be consistent with how BouncyCastle is using it in other places: OpenSSHPrivateKeyUtil and Ed25519Test.
Pure BouncyCastle
I re-wrote your test using the key pair and example signature from [RFC8037][3], which works fine as follows (Java 11):
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.Signer;
import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
import org.bouncycastle.crypto.signers.Ed25519Signer;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import static org.junit.Assert.assertEquals;
public class BouncyCastleTest {
private static final Logger LOG = LoggerFactory.getLogger(BouncyCastleTest.class);
#Test
public void testBouncyCastle() throws CryptoException {
// Test case defined in https://www.rfc-editor.org/rfc/rfc8037
var msg = "eyJhbGciOiJFZERTQSJ9.RXhhbXBsZSBvZiBFZDI1NTE5IHNpZ25pbmc".getBytes(StandardCharsets.UTF_8);
var expectedSig = "hgyY0il_MGCjP0JzlnLWG1PPOt7-09PGcvMg3AIbQR6dWbhijcNR4ki4iylGjg5BhVsPt9g7sVvpAr_MuM0KAg";
var privateKeyBytes = Base64.getUrlDecoder().decode("nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A");
var publicKeyBytes = Base64.getUrlDecoder().decode("11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo");
var privateKey = new Ed25519PrivateKeyParameters(privateKeyBytes, 0);
var publicKey = new Ed25519PublicKeyParameters(publicKeyBytes, 0);
// Generate new signature
Signer signer = new Ed25519Signer();
signer.init(true, privateKey);
signer.update(msg, 0, msg.length);
byte[] signature = signer.generateSignature();
var actualSignature = Base64.getUrlEncoder().encodeToString(signature).replace("=", "");
LOG.info("Expected signature: {}", expectedSig);
LOG.info("Actual signature : {}", actualSignature);
assertEquals(expectedSig, actualSignature);
}
}
JCA
You can also achieve the same thing using JCA, in this example my keypair are in the 'raw' format (i.e. the X and D coordinates):
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.edec.EdECObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.Security;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import static org.junit.Assert.assertEquals;
public class Ed25519JCA {
private static final Logger LOG = LoggerFactory.getLogger(Ed25519JCA.class);
#Test
public void testEd25519WithJCA() throws Exception {
Security.addProvider(new BouncyCastleProvider());
// Test case defined in https://www.rfc-editor.org/rfc/rfc8037
var msg = "eyJhbGciOiJFZERTQSJ9.RXhhbXBsZSBvZiBFZDI1NTE5IHNpZ25pbmc".getBytes(StandardCharsets.UTF_8);
var expectedSig = "hgyY0il_MGCjP0JzlnLWG1PPOt7-09PGcvMg3AIbQR6dWbhijcNR4ki4iylGjg5BhVsPt9g7sVvpAr_MuM0KAg";
// Both formatted as 32bit raw key values (x and d)
var privateKeyBytes = Base64.getUrlDecoder().decode("nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A");
var publicKeyBytes = Base64.getUrlDecoder().decode("11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo");
var keyFactory = KeyFactory.getInstance("Ed25519");
// Wrap public key in ASN.1 format so we can use X509EncodedKeySpec to read it
var pubKeyInfo = new SubjectPublicKeyInfo(new AlgorithmIdentifier(EdECObjectIdentifiers.id_Ed25519), publicKeyBytes);
var x509KeySpec = new X509EncodedKeySpec(pubKeyInfo.getEncoded());
var jcaPublicKey = keyFactory.generatePublic(x509KeySpec);
// Wrap private key in ASN.1 format so we can use
var privKeyInfo = new PrivateKeyInfo(new AlgorithmIdentifier(EdECObjectIdentifiers.id_Ed25519), new DEROctetString(privateKeyBytes));
var pkcs8KeySpec = new PKCS8EncodedKeySpec(privKeyInfo.getEncoded());
var jcaPrivateKey = keyFactory.generatePrivate(pkcs8KeySpec);
// Generate new signature
var dsa = Signature.getInstance("EdDSA"); // Edwards digital signature algorithm
dsa.initSign(jcaPrivateKey);
dsa.update(msg, 0, msg.length);
byte[] signature = dsa.sign();
var actualSignature = Base64.getUrlEncoder().encodeToString(signature).replace("=", "");
LOG.info("Expected signature: {}", expectedSig);
LOG.info("Actual signature : {}", actualSignature);
assertEquals(expectedSig, actualSignature);
}
}
Just for completeness, you can also generate the keypair using JCA in the first place, this avoids lots of format conversion:
Security.addProvider(new BouncyCastleProvider());
var keyPair = KeyPairGenerator.getInstance("Ed25519").generateKeyPair();
I am trying to write a ssl decryption program. I found some old code from 2010 and have tried to get it to run. After finally being able to compile it I now get this error:
Exception in thread "main" java.lang.IllegalAccessError: tried to access class sun.security.ssl.CipherSuite from class sun.security.ssl.decrypt at sun.security.ssl.decrypt.main(decrypt.java:36)
which is the line where I call CipherSuite.valueOf(0x00, 0x2f); (deleted some comment lines that's why it might be another line number here)
Now I have read that it would probably be a problem with the method valueOf() being private. Since I am a newbie to java I don't know how to handle that, because the code I would need to change is in a jar. (Using openjdk-1.7)
On the other hand, it says here
that the method is just static. Also checking with .isAvailable() like recommended in the link above (at the .valueOf method) does not help.
Any suggestions?
Here is the code I got so far:
package sun.security.ssl;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.BadPaddingException;
import javax.crypto.SecretKey;
import javax.xml.bind.DatatypeConverter;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import sun.security.internal.spec.TlsKeyMaterialParameterSpec;
import sun.security.internal.spec.TlsKeyMaterialSpec;
import sun.security.ssl.CipherSuite.BulkCipher;
public class decrypt {
public static void main(String[] args) throws InvalidKeyException, NoSuchAlgorithmException {
byte[] clrnd = DatatypeConverter.parseHexBinary("be9b706c800f93526913732a356c7e7fe9383ace52f5ed120d38a81db07e903d");
byte[] srvrnd = DatatypeConverter.parseHexBinary("56af786428bc3e0c69ef2fdd9f6e3456ceae660a323d6109e9554b4af7fe6652");
ProtocolVersion pv = ProtocolVersion.valueOf(0x03, 0x03);
CipherSuite cipher_suite = CipherSuite.valueOf(0x00, 0x2f);
String KeyAlgo = cipher_suite.cipher.algorithm;
String Master_Key = "c55ca8dd56fa59b80b8ff01d9a1d4f04251aec41ab6340e8db118b3d4d2ef895cc51592f9bcd5dbde5eda9d5ad386f34";
byte[] master_secret = DatatypeConverter.parseBase64Binary(Master_Key);
byte[] client_app_data = DatatypeConverter.parseHexBinary("715e388b6ed9339faa6fc640f329c358");
SecretKey masterkey = new SecretKeySpec(master_secret, 0, master_secret.length, KeyAlgo);
//Calculate connection keys
BulkCipher cipher = cipher_suite.cipher;
int expandedKeySize = cipher_suite.exportable ? cipher.expandedKeySize : 0;
KeyGenerator kg = JsseJce.getKeyGenerator("SunTlsKeyMaterial");
int pv_major = pv.major;
int pv_minor = pv.minor;
try {
kg.init(new TlsKeyMaterialParameterSpec(masterkey, pv_major, pv_minor, clrnd, srvrnd, cipher.algorithm, cipher.keySize, expandedKeySize, cipher.ivSize, cipher_suite.macAlg.size, cipher_suite.prfAlg.getPRFHashAlg(), cipher_suite. prfAlg.getPRFHashLength(), cipher_suite.prfAlg.getPRFBlockSize()));
} catch (InvalidAlgorithmParameterException ex) {
Logger.getLogger(decrypt.class.getName()).log(Level.SEVERE, null, ex);
}
TlsKeyMaterialSpec keySpec = (TlsKeyMaterialSpec)kg.generateKey();
SecretKey clntWriteKey = keySpec.getClientCipherKey();
IvParameterSpec clntWriteIV = keySpec.getClientIv();
SecureRandom clientrandom = new SecureRandom(clrnd);
CipherBox svbox = cipher_suite.cipher.newCipher(pv, clntWriteKey, clntWriteIV, clientrandom, false);
try {
svbox.decrypt(client_app_data, 5, client_app_data.length-5, 0);
} catch (BadPaddingException ex) {
Logger.getLogger(decrypt.class.getName()).log(Level.SEVERE, null, ex);
}
System.out.println(svbox);
}
}
Thanks!
I guess because it uses the default access modifier.
Java Access Modifiers
The class sun.security.ssl.CipherSuite uses the default access modifier. This means that you can only access this class from the same package, which is not the case in your project.
CipherSuite class definition