I'm trying to generate JWT but I'm receiving this error:
openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout private.key -out certificate_pub.crt
I'm using the io.jsonwebtoken.Jwts library, and a private key in string form but I'm getting errors.
Map<String, Object> payload = new HashMap<>();
payload.put("iss", orgId);
payload.put("sub", accountId);
payload.put("exp", expirationTime);
payload.put("aud", new
StringBuilder("Url").append("/c/").append(apiKey).toString());
payload.put(new StringBuilder("Url").append("/s/entt_sdk").toString(), Boolean.TRUE);
return Jwts.builder().setClaims(payload).**signWith**(SignatureAlgorithm.RS256, privateKeyStr).compact();
java.lang.IllegalArgumentException: Base64-encoded key bytes may only be specified for HMAC signatures. If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead.
My private key looks like this:
-----BEGIN PRIVATE KEY-----
sajdkjsadkjsahdkjsadksadkjsadkjs
-----END PRIVATE KEY-----
The error is quite straightforward - this method can be used only for Hmac algorihtms. For RSA based algorithms you will have to use signWith(SignatureAlgorithm, Key).
The key that I will use will not be in PEM format - it will be base64 DER encoded String - to do it I had to get rid of the beginning and ending of PEM format for this key - -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY----- :
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC9/xBGBRhpSBa/
Xc/5CeAQjjVMcXfIFOqeIc/kq7dnsLz+ntTrvTE3fyz0E8J8MLMTNypK7irt8MB0
0Gpm/NJHnLSGqgSqIXAvUbUsxCO8ULSTKLTQs4RiDbUtOWZgacvRN0IYRnN2tDWf
DWTYAElBSValTt/jvHw72BMcLXd8plGQYYD52l6+w/7ENgLG+hNbVewdM/saJKyL
Y/jhRl758XWGw5bCmIYk9BCbUVDLc5PN+iiYoBFTQYwP0Y1ln58vNZ/CDBw2mW3q
TyploxoAdmG86km4EK2mtlhWBaUshfHORxGkWlCXcwazWDoc5QJ24McaYbcQOcPW
lLDgP8MvAgMBAAECggEAHDX6MZtiE4fbsNB6J+06ctrauR4D/hZ0+8PjfX2tvty0
Q05MKTCvVSEyCI/CifQlMs43HmccwrXDrdSgZ+hURMPU3kXyaVyLrssADsSU1cpZ
9ZvOtUpidri4VR23wMsUs1z0GGylilOZvqMbfSMVvXbpQaRjhAohnDUqKT3rBvvd
fqO8KFl8FCgMEbbPvym5tJvrYfe9WalisnrGrrCZoaBmR5dEbjUfWrMg6bMcfPlz
rVaney8+UdFu15RUXTno3mu+glIYz1MyYk6LdlgBrb19gBscykqi0wYhZ312Yk0B
SXx+RIi49oZy7IU4jybzOSqWL79L4rQdvtBrp/dx6QKBgQDn/XeuCATwLdFyeo4/
ksVlhXIp9ykAZSFk/wnapcsvLSV59edI8mkslAwTTbgqPn0hvxVdwf0k23wipkGl
FiujHMCGoeT9ZwYs9uDEkGABATXomr7eC64AEfuUnUZMj3s0BgG5S/mGonlkIlTt
RpvxzMeYnRvLjDXZMP7FKCz0DQKBgQDRqPq+w7MQFBaBMTE7+685QeR+xxGnMipW
Qf579E++ihslGx6LztQnFhbah2VEVCPBq7R/BiEHiW4bA+DiTC8HnMsZi3jhlO9q
yw0DSZUSX8vsgNW2ghJOF9JnZEbptN3RlD11koSvkFZiUuxHYa0n6ti38CwfLxgV
MCuL5XOZKwKBgGi4CqD9L7V3CTdiyPk7eG1mOm1lCxYJkHR1h24yLrCB8YvHC3rr
Kbycq4K/L2WqRXPJPIzQ90L+7F77q2AozNPZM7LSO3qDWc9MNZOlFCD/+eSgjY3P
ueCAPY8NG2GN1vBZ0cdh2yYCC0e/E5TzrYsNg/+I07Yi+V+r9STsCLa1AoGAJnJo
WOcmRQKKBfLxZmCHB2bv8dergw+N9/duJWjt3rEQvUM13Ml22hwQ4M4HYfpT/EXy
eYC0Od+X01houtbhoPG9xNdwuV1Icjr+DeZGcfIjQSF3D1rW5H811EPtRRonuzEF
/DN8JX3AeZNfRM/CoxlL2J8wWB+YuPn2YlcXVbUCgYEAmVETM7+OBW9YKtv6zvKe
OZeZUIDIUZDqZgLd3IT7rikVCedIljWNhroXU1wNMssJPkfiQToGaykUMbBcgZKI
neU2IuYWaLXBN9oAj1u7/YQ0DpPqk/Sb2FpVX5eKfp4cu8XdyezxNuFFsPVdGBhB
xhqJOJuUc/ZKbo5Stc3NXEE=
Here is example of how to read this key and sign JWT with it :
//create payload
Map<String, Object> payload = new HashMap<>();
payload.put("iss", "orgId");
payload.put("sub", "orgId");
payload.put("exp", "orgId");
payload.put("aud", new
StringBuilder("Url").append("/c/").append("key").toString());
payload.put(new StringBuilder("Url").append("/s/entt_sdk").toString(), Boolean.TRUE);
// read key
String privateKeyB64 = Files.lines(Paths.get("src/main/resources/private.key")).collect(Collectors.joining());
byte[] privateKeyDecoded = Base64.getDecoder()
.decode(privateKeyB64);
//create key spec
PKCS8EncodedKeySpec spec =
new PKCS8EncodedKeySpec(privateKeyDecoded);
// create key form spec
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(spec);
//create signed JWT - JWS
String jws = Jwts.builder().setClaims(payload).signWith(SignatureAlgorithm.RS256, privateKey).compact();
System.out.println(jws);
Notice I used PKCS8EncodedKeySpec because your key seems to be in PKCS8 format. The output is :
eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJvcmdJZCIsImF1ZCI6IlVybC9jL2tleSIsImlzcyI6Im9yZ0lkIiwiZXhwIjoib3JnSWQiLCJVcmwvcy9lbnR0X3NkayI6dHJ1ZX0.m8ASk4kUNx41csikpd0zALLQTjwG2pc0Ba0D9PGLVbI2NaY0IIXgaVVVJcIERz4ejj_jfq436v6v0_QnxdmvjMAnx88UmHGdrCT0V5MZl008LP4g4LrV-WczNltCUpoJQ-4CW6xkpXD03JIDQAYwaKb-PIOtm-pfLJhPPmxykc8QioueijhI5M__Pq5Nq0JCbkQxfGzxE5m_gJwwq7n290RBGRYH6AHeClaEJhDzLNitIejNvvua4zNNC6S1CHsa4ChaEFfRb9bi-jNEQW27IGhrKRCtuwleFwigl7oTIsyaRWlzuVNYcZHS707Z2o6Mkf9hDo8AGKURUVsJgA8WIg
I tested it on Java 8. For Java 11 I received an error with missing module regarding XML processing.
Related
I am trying to convert EC private key
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIE2tzb8O0gBVw2IFOB/B8l1Ztjax3ut4DeNtuC3UMmZ6oAoGCCqGSM49
AwEHoUQDQgAEayT6Tv8zZlpIUOKHEYnmsKZyTaqOHajL0InS4c5tK4fhkHZDSWUa
3tPl1ibIXt0LvaxHk47h0Tc4SGr3Ex8Bhg==
-----END EC PRIVATE KEY-----
to Private Key
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgTa3Nvw7SAFXDYgU4
H8HyXVm2NrHe63gN4224LdQyZnqhRANCAARrJPpO/zNmWkhQ4ocRieawpnJNqo4d
qMvQidLhzm0rh+GQdkNJZRre0+XWJshe3Qu9rEeTjuHRNzhIavcTHwGG
-----END PRIVATE KEY-----
It's very easy when you execute openSsl command like this:
openssl pkcs8 -topk8 -nocrypt -in ec1.pem -out ec2.pem
But i want to do this in Java way and didn't find any solution (tried so many from stackoverflow).
So i have for now following class:
ECNamedCurveParameterSpec ecNamedCurveParameterSpec = ECNamedCurveTable.getParameterSpec("prime256v1");
KeyPairGenerator keyPair = KeyPairGenerator.getInstance("ECDSA", "BC");
// Create a secure random number generator using the SHA1PRNG algorithm
SecureRandom secureRandomGenerator = SecureRandom.getInstance("SHA1PRNG");
keyPair.initialize(ecNamedCurveParameterSpec, secureRandomGenerator);
Then i generate KeyPair and get PrivateKey in ECPrivateKey Object:
KeyPair pair =keyPair.generateKeyPair();
ECPrivateKey privateKey = (ECPrivateKey) pair.getPrivate();
StringWriter sw = new StringWriter();
try (JcaPEMWriter jcaPEMWriter = new JcaPEMWriter(sw);) {
jcaPEMWriter.writeObject(privateKey);
}
String pemFormat = sw.toString();
This string pemFormat is actually PEM format that starts with BEGIN EC PRIVATE KEY
How can i convert it just to BEGIN PRIVATE KEY?
I assume that should be a way if openSsl can do it.
A conversion from the SEC1/PEM (-----BEGIN EC PRIVATE KEY-----...) to the PKCS#8/PEM format (-----BEGIN PRIVATE KEY-----...) is not needed at all, because privateKey.getEncoded() returns the key already in PKCS#8 format. So it only needs to be exported as PEM e.g. with a PemWriter:
import org.bouncycastle.util.io.pem.PemWriter;
...
// Your code
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
...
ECPrivateKey privateKey = (ECPrivateKey)pair.getPrivate();
System.out.println(privateKey.getFormat()); // PKCS#8
// Export as PKCS#8 PEM encoded key via PemWriter
StringWriter stringWriter = new StringWriter();
try (PemWriter pemWriter = new PemWriter(stringWriter)){
pemWriter.writeObject((PemObjectGenerator)new PemObject("PRIVATE KEY", privateKey.getEncoded()));
}
String pkcs8PEM = stringWriter.toString();
System.out.println(pkcs8PEM); // -----BEGIN PRIVATE KEY-----MIGTAg...-----END PRIVATE KEY-----
You can check the format in an ASN.1 parser, e.g. https://lapo.it/asn1js.
However, if you are really looking for an explicit conversion of a SEC1/PEM key into a PKCS#8/PEM key, then the import of a SEC1/PEM key is described e.g. here. The imported key can then be exported as a PKCS#8/PEM key using a PemWriter as in the example above.
I can't generate private key with bouncycastle due to Unknown KeySpec type: java.security.spec.X509EncodedKeySpec. (However doing same for public key doesn't throw exception and works - why?)
java.security.spec.InvalidKeySpecException: Unknown KeySpec type:
java.security.spec.X509EncodedKeySpec at
org.bouncycastle.jcajce.provider.asymmetric.rsa.KeyFactorySpi.engineGeneratePrivate(Unknown
Source) at
java.security.KeyFactory.generatePrivate(KeyFactory.java:366)
PemReader pemReader = new PemReader(new InputStreamReader(new FileInputStream("private_unencrypted.pem")));
PemObject pemObject = pemReader.readPemObject();
pemReader.close();
KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
byte[] privateKeyBytes = pemObject.getContent();
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(privateKeyBytes);
PrivateKey privateKey = keyFactory.generatePrivate(x509KeySpec);
For RSA private keys you should be using PKCS8EncodedKeySpec if your key is encoded in PKCS8 format. PKCS8 format usually looks like :
-----BEGIN PRIVATE KEY-----
base64 encoded der key
-----END PRIVATE KEY-----
If your key is in PKCS1 format and looks like :
-----BEGIN RSA RIVATE KEY-----
base64 der encoded key
-----END RSA PRIVATE KEY-----
you should first convert it to PKCS8 format and then use the class mentioned above.
However doing same for public key doesn't throw exception and works - why?
Because public keys, which are usually part of Certificates, are encoded in X509 format, however private keys are usually encoded in PKCS format.
I have a PEM formatted public key (received from an iOS device):
String pemPubKey = ""+
"-----BEGIN RSA PUBLIC KEY-----\n"+
"MIIBCgKCAQEA07ACQHTTrgX7ddNtyamh58xwD+S+pSrJz/Rah4zj0HIg4V/Ok5vk\n"+
"Wx6y4UyuKLCtefeiB2ipg/n1ZZ0eRac1B4UwPhAtILGQzgIUgOp0cQ3Cb94ugq92\n"+
"wxkxeEdWmIFIlXgWOf6I8yWp9DZaigrRhA2kPbY01zKxCsX1ZxKMVu2sU/HM1hJy\n"+
"aebLLND002yLzuRDLXbacmCt5U6vDQDjBmm3uZ26fEMF+GTCnn6fJBq5RDfRKjpS\n"+
"fVM0mCePO9RHiwu3oHfqoyLA2QGlCexXcIYq7KbJjC9vcamAWRqQdHlsSj5ezDTR\n"+
"GofA6HtQ+zNdGHOvqsYtbN8MJSlUXXy39wIDAQAB\n"+
"-----END RSA PUBLIC KEY-----";
If I try to parse it into a PublicKey using KeyFactory like that:
KeyFactory kf = KeyFactory.getInstance("RSA");
Pattern parse = Pattern.compile("(?m)(?s)^---*BEGIN.*---*$(.*)^---*END.*---*$.*");
String encoded = parse.matcher(pemPubKey).replaceFirst("$1");
byte[] pem = Base64.getMimeDecoder().decode(encoded);
PublicKey pubKey = kf.generatePublic(new X509EncodedKeySpec(pem));
I get: java.security.InvalidKeyException: IOException: algid parse error, not a sequence.
But when I use bouncycastle like that:
SubjectPublicKeyInfo subjectPublicKeyInfo =
(SubjectPublicKeyInfo) new PEMParser(new StringReader(pemPubKey)).readObject();
PublicKey pubKey;
if (PKCSObjectIdentifiers.rsaEncryption == subjectPublicKeyInfo.getAlgorithm().getAlgorithm()) {
DLSequence der = (DLSequence) subjectPublicKeyInfo.parsePublicKey().toASN1Primitive();
ASN1Object modulus, exponent;
modulus = (ASN1Object) der.getObjectAt(0);
exponent = (ASN1Object) der.getObjectAt(1);
RSAPublicKeySpec spec =
new RSAPublicKeySpec(new BigInteger(modulus.toString()), new BigInteger(exponent.toString()));
KeyFactory factory = KeyFactory.getInstance("RSA");
pubKey = factory.generatePublic(spec);
} else {
// Throw some exception
}
I get a valid PublicKey and the algorithm is identified correctly.
Why does java's parser fails here? And am I doing the migration from SubjectPublicKeyInfo to PublicKey correctly?
Update:
I've tried to verify the key using openssl:
$ openssl rsa -inform PEM -pubin -in pub.pem -text -noout
unable to load Public Key
140735659656136:error:0906D06C:PEM routines:PEM_read_bio:no start
line:/BuildRoot/Library/Caches/com.apple.xbs/Sources/libressl/libressl-22.50.2/libressl/crypto/pem/pem_lib.c:704:Expecting:
PUBLIC KEY
And after removing RSA from the header / footer:
$ openssl rsa -inform PEM -pubin -in pub.pem -text -noout unable to
load Public Key 140735659656136:error:0D0680A8:asn1 encoding
routines:ASN1_CHECK_TLEN:wrong
tag:/BuildRoot/Library/Caches/com.apple.xbs/Sources/libressl/libressl-22.50.2/libressl/crypto/asn1/tasn_dec.c:1164:
140735659656136:error:0D07803A:asn1 encoding
routines:ASN1_ITEM_EX_D2I:nested asn1
error:/BuildRoot/Library/Caches/com.apple.xbs/Sources/libressl/libressl-22.50.2/libressl/crypto/asn1/tasn_dec.c:314:Type=X509_ALGOR
140735659656136:error:0D08303A:asn1 encoding
routines:ASN1_TEMPLATE_NOEXP_D2I:nested asn1
error:/BuildRoot/Library/Caches/com.apple.xbs/Sources/libressl/libressl-22.50.2/libressl/crypto/asn1/tasn_dec.c:653:Field=algor,
Type=X509_PUBKEY 140735659656136:error:0906700D:PEM
routines:PEM_ASN1_read_bio:ASN1
lib:/BuildRoot/Library/Caches/com.apple.xbs/Sources/libressl/libressl-22.50.2/libressl/crypto/pem/pem_oth.c:84:
Java's parser didn't fail, your public key is not an instance of an encoded SubjectPublicKeyInfo structure that Java's X509EncodedKeySpec is expecting. I haven't gone through the Bouncycastle routines to see why it succeeded, but PEMParser is designed to parse many different kinds of so-called "PEM" files.
A SubjectPublicKeyInfo is defined in RFC 5280 as:
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING
}
Your public key is a simple PKCS#1 RSAPublicKey, defined in RFC 8017 as:
RSAPublicKey ::= SEQUENCE {
modulus INTEGER, -- n
publicExponent INTEGER -- e
}
And just a final word about "PEM" files. You need to be careful when describing something as "PEM" format because PEM is not really a specific format. "PEM" is little more than encoding a true format in base64, then wrapping the base64 in "-----BEGIN -----" and "-----END -----" lines, where hopefully uniquely describes what the base64-encoded data is.
I'm trying to calculate the hash of a public key to match what ApplePay is giving me. I can't get BouncyCastle in C# to give me the public key in the right format so I can hash it. However the Java version that I'm porting works just fine. OpenSSL also gives the right response, but I'd prefer not to have to deal with OpenSSL as well as BC to do this.
Java: (works)
ECPublicKey key = (ECPublicKey) certificate.getPublicKey();
byte[] keyBytes = key.getEncoded();
MessageDigest messageDigest = MessageDigest.getInstance("SHA256", PROVIDER_NAME);
byte[] keyHash = messageDigest.digest(keyBytes);
String base64keyBytes = new String(Base64.encode(keyBytes));
//base64keyBytes == MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+4wQWWRnPqGlsncZX17t0CfLOl6u
// 68aXUsqnzlIcpCdDukHhxibd2MjHPFGpnK3ZKdHxIFh+NBQvGssM5ncm1g==
// line break added for readability
OpenSSL: (works)
openssl x509 -noout -pubkey -in <file> -inform der
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+4wQWWRnPqGlsncZX17t0CfLOl6u
68aXUsqnzlIcpCdDukHhxibd2MjHPFGpnK3ZKdHxIFh+NBQvGssM5ncm1g==
-----END PUBLIC KEY-----
C#: (keyBytes are wrong)
SubjectPublicKeyInfo pubinfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(certificate.GetPublicKey());
byte[] keyBytes = pubinfo.GetEncoded();
byte[] keyHash = DigestUtilities.CalculateDigest("SHA_256", keyBytes);
string keyBytesEncoded = Convert.ToBase64String(keyBytes);
/*keyBytesEncoded == MIIBKjCB4wYHKoZIzj0CATCB1wIBATAsBgcqhkjOPQEBAiEA/////wAAAAEAAA
AAAAAAAAAAAAD///////////////8wWwQg/////wAAAAEAAAAAAAAAAAAAAAD/
//////////////wEIFrGNdiqOpPns+u9VXaYhrxlHQawzFOw9jvOPD4n0mBLAx
UAxJ02CIbnBJNqZnjhE50mt4GffpAEIQNrF9Hy4SxCR/i85uVjpEDydwN9gS3r
M6D0oTlF2JjClgIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8YyVRAg
EBA0IABPuMEFlkZz6hpbJ3GV9e7dAnyzperuvGl1LKp85SHKQnQ7pB4cYm3djI
xzxRqZyt2SnR8SBYfjQULxrLDOZ3JtY=
line break added for readability */
What is the right way to port the GetEncoding?
I know it's the key data that wrong because when I manually force in the data from openssl I get the right hash.
EDIT: Added the out puts.
I think it would just be:
byte[] keyBytes = certificate.CertificateStructure.SubjectPublicKeyInfo.GetDerEncoded()
My task: I have encrypted (RSA) data and public key as modulus and exponent. I have to write decryption code.
My problem with it: My implementation doesn't work ;) As far as I know philosophy is simple "open text" == rsa(public_key, rsa(private_key, "open text")) Edit: Exactly my assumption was wrong (Assumption is mother of all fu..ups ;) ). It should be "open text" == rsa(private_key, rsa(public_key, "open text")) because in RSA, public key is used for encryption and private for decryption.
I assumed that I can have public key which doesn't correspond to private key using during encryption so for tests I created own keys in such way:
openssl genrsa -des3 -out server.key 1024
openssl req -new -key server.key -out server.csr
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
I got public key modulus and exponent using command:
openssl x509 -in server.crt -text
For encryption testing I'm using code
//Reads private key from file
//StringPasswordFinder is my tmp implementation of PasswordFinder
PEMReader pemReader = new PEMReader(new FileReader("/path/to/server.key"), new StringPasswordFinder());
KeyPair keyPair = (KeyPair) pemReader.readObject();
PrivateKey pk = keyPair.getPrivate();
//text for encryption
String openText = "openText";
//encryption
Cipher rsaCipher = Cipher.getInstance("RSA", "BC");
rsaCipher.init(Cipher.ENCRYPT_MODE, pk);
byte[] encrypted = rsaCipher.doFinal(openText.getBytes("utf-8"));
And for decryption of encrypted text I use code
//modulus hex got using openssl
byte[] modulus = Hex.decodeHex("very long hex".toCharArray());
//exponent hex got using openssl
byte[] exponent = Hex.decodeHex("010001".toCharArray());
//initialization of rsa decryption engine
RSAEngine rsaEngine = new RSAEngine();
rsaEngine.init(false, new RSAKeyParameters(false, new BigInteger(modulus), new BigInteger(exponent)));
//input - encrypted stream
ByteArrayInputStream bais = new ByteArrayInputStream(encrypted);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//reading blocks from the input stream and decrypting them
int bytesRead = 0;
byte[] block = new byte[rsaEngine.getInputBlockSize()];
while ((bytesRead = bais.read(block)) > -1) {
baos.write(rsaEngine.processBlock(block, 0, bytesRead));
}
//dispalying decrypted text
System.out.println(new String(baos.toByteArray(), "utf-8"));
And after all displayed text is not. Can anybody show me where I'm wrong?
Edit: Summing up this problem has no solution. Because it's not possible encrypt message using private key and later decrypt it using public one. At general I mixed up encryption with signing message and decryption with verification. Because during making signature private key is used and public is used during verification. Btw, MByD thx for important clue.
I am not so familiar with java libraries for RSA, the times I tried to implement RSA in java was to build all calculations by myself, but if I understood you correct, I see 2 problems:
the data should be encrypted with the public key and decrypted with private key, not the other way around (since everyone with public key will be able to decrypt it...)
the public key should match the private key, otherwise, anyone with any private key will be able to decrypt data encrypted with any public key...
Also, for very long data, you should not use public key encryption. Instead, encrypt the data in some other algorithm (RC4, AES, etc.) and encrypt the key in RSA (similar to PGP approach)