php: string encryption using RSA private key (convert from JAVA to php) - java

I have this JAVA code & I need to write the same thing in php:
public static String signMsg(String msg, String privateKey)
throws Exception {
byte[] bytes = Base64.getDecoder().decode(privateKey);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
Signature ps = Signature.getInstance("SHA256withRSA");
ps.initSign(kf.generatePrivate(spec));
ps.update(msg.getBytes("UTF-8"));
byte[] sigBytes = ps.sign();
return Base64.getEncoder().encodeToString(sigBytes);
}
Any idea how to do that?
Thanks in advance :)

Regarding your first approach: A signature is created with the private key. The public key is used to verify the signature. Regarding your second approach: A HMAC is not the same as a signature.
The Java code loads a private key in PKCS8 format, PEM encoded without header and footer. In the PHP code the key can be read in the same format and encoding. Alternatively, the key can be loaded in PKCS#1 format. Regarding the encoding, a PEM or DER encoded key is also accepted.
Additionally, it must be specified which algorithm is used for signing. The Java code applies RSA with PKCS#1 v1.5 padding and SHA-256 as digest. Furthermore, the generated signature is Base64 encoded. In order for the PHP code to provide the same RSA signature, the same parameters must be used.
Note that signing does not necessarily generate the same signature using the same message and the same key. It depends on the algorithm. However, in the case of RSA with PKCS#1 v1.5 padding, always the same signature is generated (deterministic). For PSS, on the other hand, a different signature is generated each time (probabilistic).
The following PHP code uses the PHPSECLIB and generates the same signature as the Java code:
use phpseclib3\Crypt\RSA;
$privateKey= 'MIIEvg...';
$signatureB64 = base64_encode( // Base64 encode signature
RSA::load($privateKey)-> // Choose RSA, load private PKCS8 key
withHash('sha256')-> // Choose SHA-256 as digest
withPadding(RSA::SIGNATURE_PKCS1)-> // Choose PKCS#1 v1.5 padding
sign('The quick brown fox jumps over the lazy dog') // Sign messsage
);
print($signatureB64);

Related

RSA decryption according to the private key reports an error

My code using RSA on the front end:
const rsa = new JSEncrypt();
rsa.setPublicKey(k);
const resultText = rsa.encrypt("violet");
console.log(resultText);
My code using RSA in the backend:
byte[] inputByte = org.apache.commons.codec.binary.Base64.decodeBase64(str.getBytes("UTF-8"));
byte[] decoded = org.apache.commons.codec.binary.Base64.decodeBase64(privateKey);
PrivateKey priKey = KeyFactory.getInstance("RSA").generatePrivate(new
PKCS8EncodedKeySpec(decoded));
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE,priKey);
String outStr=new String(cipher.doFinal(inputByte));
return outStr;
PublicKey like this:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA13gYdCmOjR9yqQD7ldzG
ZXabSon6SiLceCK6vRXf4NMbF+EQke0vRpqU3IZ/S1pFdvoQswQabsA4zf0WACVT
iaGIhWDlPu3mecri8rYtmOSfd8GCE0vEgFNvSD6IXRLPeLCB+i7WENBa4fCEtW8W
Hzdas96CLiESbjSAruRasQXP2OLqEA2GU83/069vh8uRKzui+yw0aAXZFyFyFRFa
lxYltFadVpz3+kBplvpzuj82t4fc3yCRbrpeRyTyX1sz0ULSxx/k3/p1OuJtIq9Y
9uN0G4gxhcDFJ4L41uXOln5CPapk7tlsYobhhvxYHw1rrweY+06hrQ7r0Hblv2nH
GQIDAQAB
-----END PUBLIC KEY-----
PrivateKey like this:
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEA13gYdCmOjR9yqQD7ldzGZXabSon6SiLceCK6vRXf4NMbF+EQ
ke0vRpqU3IZ/S1pFdvoQswQabsA4zf0WACVTiaGIhWDlPu3mecri8rYtmOSfd8GC
E0vEgFNvSD6IXRLPeLCB+i7WENBa4fCEtW8WHzdas96CLiESbjSAruRasQXP2OLq
EA2GU83/069vh8uRKzui+yw0aAXZFyFyFRFalxYltFadVpz3+kBplvpzuj82t4fc
3yCRbrpeRyTyX1sz0ULSxx/k3/p1OuJtIq9Y9uN0G4gxhcDFJ4L41uXOln5CPapk
7tlsYobhhvxYHw1rrweY+06hrQ7r0Hblv2nHGQIDAQABAoIBAAyqFmXde294BblB
QYhRbafRDNeYvIlW+zZkdC1g98OzJMiGhf7NvhWcSFud3CWFrMeNcyXSe+s+iRgy
Y/SmPP6969RLGa5VNVK7RhOV+aAe7/COAyM3NNmGDehlJIaz8FXbqggWcKaUWIMn
K+WuHdK/4ijoTyZ+8yJfG6Er8tisryLQ9io9+op9g/ZWzaUKgu934/cDxUt70bfm
x+ZEPi6YfkJ1uOpXnnadDyw2RUDcvCM3fK3KF5fqM7SJAXY9b1pmLr+Ccn1qkT9G
I+QHidEsGfJciX5AoHnlIMLPMVIPKBbq4GwC/Ngb41LprNJWlPR38N2ySjky/Jyt
159XWHECgYEA9lx2KfFmyLyVjnkIF3JI50mSZAw4YPBBqB27UInacvxXbjfVURht
xK60GB9OkDbFdeNh89x86Gfwvm5bTq4W8YSH4Obd5Fg8XjTuaicTi03CSfF5SdJn
JLLOUmlqP75gkbEPNUoOfqhqq6IbyJVB3egyL90cd2/wCdJOVLEUly8CgYEA3+Y4
lNdl0xedkDNkWsCyyA4iPSUzVxsuch9hW/VGBwzga8rtiNllpKifVvctQOEu/KUe
vVQRF78ojZaMGT33l6TivnUL54z9Lo9uWghoG8TqMfnG34pFPe3R+zvGP87Hrozw
1EUhiMT198SlB/YHrgGGGlJbG+rlm5GIx3lEdDcCgYA4RSw0LlA0v2ZFkX14pYDj
WxmVwnjKI3ZLqObU4XfE1cA+i4AssrC3wNOfwt7V77ywTYxc/9qD1uHVDS3LzdWt
uoCyrOi3tDOtrNdb5asAIXWkIAR9CRLH/hNEHZHIF3rFLDT2DgE7iso6g59m9DiE
L/nulsleunGQPLnpfDzgvwKBgDRV5Q3tl3CTSZJGYQPRnTikDR7LzkdjJCUq7qAH
IhpNyTuJEKL3ZgnqHGzAlERhHpGRqzDIMMKjPUBzW0YfNPuuYA3y4Bh83UV/42SK
KIOtMK0D3JeuA2dparbWDw4lMIm9iiGkEyWcHH6Q6Z6FxN/InWcTrxZEfu0xRI6T
6wtbAoGAfl5dW9LNoaNfQbgIq+PJKZm9f1frza55mFTJgo3ravdb3GmzWVHN8xRf
nLKyKyNLqbkT35IGc39NkALJLxT5RibkAZLiUiwqdMF63sgODbA9AGTmhe+JHS+V
hBmFnCyp6UiN9E4ZAWcZQILa0rRMftMFngAJ3El0ZP+HziRnNzs=
-----END RSA PRIVATE KEY-----
Bu-t, when i do the java code decryption, it reported such an error:
java.security.InvalidKeyException: IOException : DerInputStream.getLength(): lengthTag=111, too big.
How can i solve this problem ?
1. You are decoding wrong. PEM format has a dash-BEGIN line identifying the type of data, a block of base64 encoding the data, and a dash-END line. The BEGIN and END lines are part of the format, but they do not contain base64-encoded data; only the lines in between contain the base64-encoded data. You are apparently passing the whole thing, including the BEGIN and END lines, to commons.codec.Base64, which results in decoding a bunch of garbage before and after the actual data. That garbage isn't valid ASN.1 DER, so when Java tries to parse it as DER it fails.
2. Plus your data is not a PKCS8-clear privatekey. The PEM type 'RSA PRIVATE KEY' is an OpenSSL-defined format that contains a 'traditional' or 'legacy' format, namely the PKCS1 representation of the private key. This is not PKCS8, which is the only key format Java supports natively; that's why the spec class is named PKCS8EncodedKeySpec, because it is a key spec encoded as PKCS8 and more specifically PKCS8-clear. If you fix the above problem by removing the BEGIN and END lines before base64-decoding, Java can parse the result as DER, but not as a PKCS8-clear key; you get a different exception about 'algid parse error, not a sequence'. To fix this there are 5 approaches:
change whatever process you use to initially generate the keypair so it generates PKCS8, not OpenSSL-legacy PKCS1. Especially since you need anyway to replace the keypair you compromised by publishing it, as 207421 said. You give no clue what that process is or was, so I can't give any details.
convert your generated privatekey, or a copy, to PKCS8-clear. This is not programming or development and offtopic, but if you have or get OpenSSL (on the same or any accessible and secure system), you can do
openssl pkey -in oldfile -out newfile # 1.0.0 up only, but older is now rare
# or
openssl pkcs8 -topk8 -nocrypt -in oldfile -out newfile # even ancient versions
Once you have a PKCS8-clear file, just remove the BEGIN and END lines and base64-decode what is left, and pass that to KeyFactory as PKCS8EncodedKeySpec as you already do.
use https://www.bouncycastle.org . The 'bcpkix' jar has (Java) code to read a large range of OpenSSL-supported PEM formats, including the RSA-PKCS1 private key format you have. There are lots of existing Qs about this; just search for PEMParser and JcaPEMKeyConverter.
convert it yourself. Decode the body of the file you have, after removing the BEGIN and END lines, to get the PKCS1 key, then build the PKCS8 format for that key, and then pass it to KeyFactory as PKCS8EncodedKeySpec. See answers by Noa Resare and Jean-Alexis Aufauvre on Getting RSA private key from PEM BASE64 Encoded private key file or mine in Java: Convert DKIM private key from RSA to DER for JavaMail .
do it entirely yourself. Decode the file you have without BEGIN/END to get PCKS1, parse that as DER following e.g. RFC8447, and build RSAPrivateCrtKeySpec. Some other As on the Q I linked just above do this. However, this requires either: using undocumented internal sun.* classes, which used to work in Java (hence the existing answers) but which 'modular' Java versions (9 up) since 2017 have steadily made more difficult or impossible; using BouncyCastle which has documented (and good) support for ASN.1 -- but then it's easier to use bcpkix for the whole job as above; or writing your own ASN.1 parsing, which is a good deal of work.
PS: encrypting text with RSA is usually a bad design; it's not suited for that. But that's not really a programming issue and doesn't belong here.

Generating public key instance for JWT validation

today we changed on from the HMAC to the RSA on our login server so I have to change the code in our Andoid app to work with that. Problem is when I try to generate a RSA public key instance from a string, all I get is java.security.spec.InvalidKeySpecException: com.google.android.gms.org.conscrypt.OpenSSLX509CertificateFactory$ParsingException: Error parsing public key exception and I have no idea what am I doing wrong.
Code I use is pretty simple:
val base64KeyString ="LS0tLS.......BASE 64 PUBLIC KEY STRING..........LRVktLS0tLQ=="
val keySpec = X509EncodedKeySpec(base64KeyString.toByteArray())
val keyFactory = KeyFactory.getInstance("RSA")
val pubKey = keyFactory.generatePublic(keySpec) //<= this is where the exception is thrown
Thanks for any help
Java crypto uses actual data on its APIs not base64, so as MichaelFehr commented you need to base64-decode your string; in standard Java (8 up) this can be done with java.util.Base64 but I don't know for Android. But less obviously, your base64 string is wrong -- it is NOT the base64 encoding of a publickey, but rather the encoding of something that begins with 4 bytes with value 2D (which is the character code for hyphen) and ends with 7 bytes which are the character codes for "EY-----" -- in other words, it appears to be the base64 encoding of a slightly broken PEM file (that might possibly encode a publickey) not an actual publickey structure.
As a working example, given the following keypair represented by the (OpenSSL-compatible and widely used) PEM encodings
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAsYU/MoIxYeVuCFcrX+fEH5XX/he/nJTSQ6P4RF38DKLNnNtQ
9XfnaMEPk2EXgSIHk+VXhE9vZNxsV7Emjg6C+eRxUB1NDBtWGX/Ts2cOYD8ZhSum
hOplKKEmdEkzGU/Rem3myfB2BQeN6RSRFigZfsR2I4oaQ5YDWJ3uk6Ix0XN4zQwm
tw1t9W9tgDZATDLW0+8uU02Wc2HrIcyOvDqJDXz/XVY+F+pkrws/ygOhxtVdNY7r
3meSKjSUVkOiXMdaqK+20l/YrvoZRVe6HEITpu9t/x6YDaoTThsbptjkAKBYpSG2
UeXJnnQMyKD2GT3Ouyiupv7lPovvLZZiqfVIWQIDAQABAoIBAGilPKjgmI21+pQO
FrKVZEaeRVIy27BJBl37Rbm/kXo9ammh22qoQRC+Zhkh7gIgdkkXs+x6Cxdw3Kkg
87JgGZOHt8hbTXTqlGm5sakbAh14dOnM3n+R0QoPXSTFlcrBA6JhMOAv0NKMLnqM
ti5Sex0AYQf4PB1FTDmr99ytiyKFK8bvAPiirKB4cEdbMTrxn6rtY324dN4PKdHR
7Sq9JnpYqOoY9adNlhPuN5vq91LCIjoDDkrZ8E8T8o9hUsJXFwsW0GDf4M7s8krj
Dt37zUPh6HSEg/Q1wM+ncEcE3PABeYvIibngfmcwVaMo/0T/9RjQL0EPUOuqxCg+
UlGyFFECgYEA63/vwM+uEil+4Zwqx7dyQ2gbNypEqNnbKelSKj0RpjJvkO7oQ39A
fcr198T1giwNo0cj3oUaduMtPBJUdpUqEYs/p1eNwcGbYSI5csLKy1awHIX5YIDY
oouIq5qipZPYXGFe5bsIZV2kRvzH/e1pW4/eDVgojVD3vKib9nPsi10CgYEAwPlB
KYu3uH5Di2UqNRAryAMCogJzCKHbC0xEMeEhOy0sWvblT1BaqrS941RuAbiaVBn/
nzltKF1WmYHfX2OK6GummYmYkT8QB1TC6ir9etlT54h/w7Hjc3eysGoCLGUr2V/q
kEUou4ut2KiVfUUxyG05Ec3BtyZ+4ZV0fdnAXS0CgYB9j2LHDHOk31b09wygGyDi
65jHGtQsqqr9d5cFSjYkxHNTdO2FP02lRBdMmUjEtLQ4v+9R7umjRZCSnLtH1lPt
sq6njDjae11atqKmm1EAhSG0s4G4gDoAwCCIThQ179PFvWyZU9UwJnM2HgSLUI+B
7/zWZJCKeAb+IW+2QSx3SQKBgEo3UqVc09LD1Mxmov33cy8gYUHXuVAnl6vXsB6S
3An7TKTLcdO4LraafrFQhmJpEgmoWhRtrJqqkyTEuxjfmsvaox9HuQKbg1mecrxG
vmgh71ALwj+MSdLdxT5t9toGKwAWEItd3dWLWdgKtjuQgh34S8uaG5eJ2aB+r2e7
eG0dAoGAX/kgK4WBBH/Paa0g4l5cHD99Lgq02IxCMODG73Vbujxu72spJElQzZGd
eyD3XjHwpa7uL6hzIcXXSpfjLxBCQRu37s7KXbYRWC7YBhV6RlUZlPkrMKr5d+68
vx5dwrelvikoKvhf3sCRbHgD0xdJtUkx0eOD/5kJAbWuh/VWJxE=
-----END RSA PRIVATE KEY-----
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsYU/MoIxYeVuCFcrX+fE
H5XX/he/nJTSQ6P4RF38DKLNnNtQ9XfnaMEPk2EXgSIHk+VXhE9vZNxsV7Emjg6C
+eRxUB1NDBtWGX/Ts2cOYD8ZhSumhOplKKEmdEkzGU/Rem3myfB2BQeN6RSRFigZ
fsR2I4oaQ5YDWJ3uk6Ix0XN4zQwmtw1t9W9tgDZATDLW0+8uU02Wc2HrIcyOvDqJ
DXz/XVY+F+pkrws/ygOhxtVdNY7r3meSKjSUVkOiXMdaqK+20l/YrvoZRVe6HEIT
pu9t/x6YDaoTThsbptjkAKBYpSG2UeXJnnQMyKD2GT3Ouyiupv7lPovvLZZiqfVI
WQIDAQAB
-----END PUBLIC KEY-----
and a token created on jwt.io, the following (Java) code using jjwt verifies and parses the token:
String pubkeyb64 = // note BEGIN/END lines and linebreaks from PEM removed
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsYU/MoIxYeVuCFcrX+fE"+
"H5XX/he/nJTSQ6P4RF38DKLNnNtQ9XfnaMEPk2EXgSIHk+VXhE9vZNxsV7Emjg6C"+
"+eRxUB1NDBtWGX/Ts2cOYD8ZhSumhOplKKEmdEkzGU/Rem3myfB2BQeN6RSRFigZ"+
"fsR2I4oaQ5YDWJ3uk6Ix0XN4zQwmtw1t9W9tgDZATDLW0+8uU02Wc2HrIcyOvDqJ"+
"DXz/XVY+F+pkrws/ygOhxtVdNY7r3meSKjSUVkOiXMdaqK+20l/YrvoZRVe6HEIT"+
"pu9t/x6YDaoTThsbptjkAKBYpSG2UeXJnnQMyKD2GT3Ouyiupv7lPovvLZZiqfVI"+
"WQIDAQAB";
String jws = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.ry5trgLoSgYZt17NOttSkyFhXIKBHjw3OZy8SK_uwukUCMCSh6O5njlRhTA306lXTFrUkb4VCKYk43y_3vTBDzNA7aAZEuwl0pL2O2gfvvcytXnHsnkjaPiigUvNF-pqpvJkOXhTXobKq-76m7ozHTabxbIO0OM4PTkte_2c2922U22qAFuVmfuKRqr3rsMwp24QImppmWsa_rElSqGhc6wIDGwrtdTIFe311Fi8o7L7wWIswRvH4WlsLG6fLGv6yNt-e2Ld5X1kAfozaZqy-hdR9vpq9LqWVWId6VhJ4kayPVnImevctyMY14TlVlwJdRBBgTAdVVl9QHFbMgwozg";
byte[] pubkeyder = Base64.getDecoder().decode(pubkeyb64);
PublicKey pubkey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(pubkeyder));
Claims parsed = Jwts.parserBuilder().setSigningKey(pubkey).build().parseClaimsJws(jws).getBody();
System.out.println (parsed);
Modification to Kotlin, if you need that, should be trivial.

Java Implementation of RSA Signature using RSAwithSHA256

I want to sign a message using RSA private key. This message will be verified by someone else maybe using another programming language other than Java.
I will use the java.security.Signature class to sign the messsage.
The code will be :
Signature privateSignature = Signature.getInstance("SHA256withRSA");
privateSignature.initSign(key); //RSA private key
privateSignature.update(text.getBytes("UTF8")); //text is the clear text string
byte[] signature = privateSignature.sign();
return Base64.getEncoder().encodeToString(signature);
When I check the result and used the publickey to decrypt the signature, I found that padding bytes are added before the SHA256 digest.
My Question is : is the resulted signature generated in a standard way so that it can be verified with the public key in widely used programming languages or is it java specific?

RSA SignatureException: Signature length not correct

I am having issues signing an rsa signature. I have a signature that has been encrypted with a private key. I have an issue when trying to validate it with the public key however. I get the following exception:
java.security.SignatureException: Signature length not correct: got 336 but was expecting 128
at sun.security.rsa.RSASignature.engineVerify(RSASignature.java:189)
at java.security.Signature$Delegate.engineVerify(Signature.java:1219)
at java.security.Signature.verify(Signature.java:652)
at XmlReader.main(XmlReader.java:65)
I have retrieved my signature and public key the following way:
BigInteger modulus = new BigInteger(Base64.getDecoder().decode(publicKeyString));
BigInteger exponent = new BigInteger(Base64.getDecoder().decode("AQAB"));
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, exponent);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey pubKey = keyFactory.generatePublic(keySpec);
byte[] sigToVerify = Base64.getDecoder().decode(signatureString);
Signature sig = Signature.getInstance("MD5WithRSA");
sig.initVerify(pubKey);
boolean verifies = sig.verify(sigToVerify);
The application fails at the last line. Any thoughts as to where this exception is caused?
UPDATE:
Added data for signature to be verified:
String data = "...." //hidden since sensitive data
byte[] dataBytes = Base64.getEncoder().encode(data.getBytes());
dataBytes = Base64.getDecoder().decode(dataBytes);
Before calling sig.verify(sigToVerify) you should call
sig.update(data);
passing the data you're verifying signature for.
And make sure that calling verify in your argument you have signature bytes only.
Well, the signature size is obviously not correct. This means that at least the signature is not formatted correctly; an RSA signature by itself is always the size of the modulus in bytes (identical to the key size, in your case a short 1024 bit key).
algrid is right that usually you'd have to update the signature first. So it would be logical that the input of the signature is prefixed with the data that was signed. In that case the last 128 bytes are probably the signature, and the bytes before that are the data.
It is also possible that the signature is not a "raw" signature but a formatted signature, e.g. using the PKCS#7 / CMS syntax or PGP syntax. In that case you would need an library to decode it.
Note that you cannot verify a signature without the data that was signed, or at least the hash over the data. You can only check the correctness of the signature in that case (by validating the padding performed before modular exponentiation for RSA, in case we need to go into details).

How to decrypt a private key in Java (without BC openssl)

Is it possible decrypt an encrypted RSA (or others, shouldn't matter) private keys using JCE and/or BouncyCastle provider (not using openssl bundle)?
I can read unencrypted keys just fine using PrivateKeyFactory.
Googling this gets me through examples of using PEMReader (from BC openssl bundle) that has a password applied to it, but - don't want to use openssl bundle, don't necessarily want to use PEM format, and I can decode PEM using PemReader (from provider bundle). It's what can I do with it afterwards is the question.
I'm looking for some mega-function, or a series thereof that can do it, i.e. I am not looking into parsing the ASN1 of the encrypted key, figuring out the encryption method, passing the input through the cipher, etc.
If you have an encrypted PKCS#8 key in binary format (i.e. not in PEM format) the following code shows how to retrieve the private key:
public PrivateKey decryptKey(byte[] pkcs8Data, char[] password) throws Exception {
PBEKeySpec pbeSpec = new PBEKeySpec(password);
EncryptedPrivateKeyInfo pkinfo = new EncryptedPrivateKeyInfo(pkcs8Data);
SecretKeyFactory skf = SecretKeyFactory.getInstance(pkinfo.getAlgName());
Key secret = skf.generateSecret(pbeSpec);
PKCS8EncodedKeySpec keySpec = pkinfo.getKeySpec(secret);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(keySpec);
}
If you have a PEM format, remove the header (first line), the footer(last line) et convert the remaining content from base64 to regular byte array.

Categories