I use this to create a private key and encrypt:
var forge = require('node-forge');
var fs = require('fs');
var bytes = forge.random.getBytesSync(16);
console.log("random plaintext", forge.util.bytesToHex(bytes));
var keypair = forge.pki.rsa.generateKeyPair({bits: 2048, e: 17});
var encrypted = keypair.publicKey.encrypt(bytes, 'RSA-OAEP', {
md: forge.md.sha256.create(),
mgf1: {
md: forge.md.sha1.create()
}
});
console.log("ciphertext", forge.util.bytesToHex(encrypted));
fs.writeFileSync('ciphertext', encrypted);
var pem = forge.pki.privateKeyToPem(keypair.privateKey);
fs.writeFileSync('prikey.pem', pem);
console.log("private key", forge.util.bytesToHex(pem));
I then try decrypting using either:
public class CryptoTest {
public static void main(String[] args) throws Exception {
PEMParser parser = new PEMParser(new FileReader("/tmp/prikey.pem"));
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
KeyPair kp = converter.getKeyPair((PEMKeyPair) parser.readObject());
RSAPublicKey pubkey = (RSAPublicKey) kp.getPublic();
RSAPrivateKey privkey = (RSAPrivateKey) kp.getPrivate();
byte[] ct = Files.readAllBytes(Paths.get("/tmp/ciphertext"));
Cipher oaepFromInit = Cipher.getInstance("RSA/ECB/OAEPPadding");
OAEPParameterSpec oaepParams = new OAEPParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-1"), PSpecified.DEFAULT);
oaepFromInit.init(Cipher.DECRYPT_MODE, privkey, oaepParams);
byte[] pt = oaepFromInit.doFinal(ct);
}
}
or
openssl rsautl -decrypt -inkey prikey.pem -oaep -in ciphertext -out plaintext
The Java code throws the error:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: too much data for RSA block
at org.bouncycastle.jcajce.provider.asymmetric.rsa.CipherSpi.engineDoFinal(Unknown Source)
at javax.crypto.Cipher.doFinal(Cipher.java:1966)
at cryptotest.CryptoTest.main(CryptoTest.java:47)
and OpenSSL says:
RSA operation error
140258189264528:error:0406506C:rsa routines:RSA_EAY_PRIVATE_DECRYPT:data greater than mod len:rsa_eay.c:518:
What could be the problem?
It is necessary to keep the coded message in the binary encoding:
fs.writeFileSync('ciphertext', encrypted, {encoding: 'binary'});
Related
I'm trying to encrypt data in my C# client with public RSA key from the Java server. To do this I generated KeyPair in Java
KeyPairGenerator.java
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair kp = kpg.generateKeyPair();
Key pub = kp.getPublic();
Key pvt = kp.getPrivate();
String outFile = "rsa_key";
FileOutputStream outPvt = new FileOutputStream(outFile + ".key");
outPvt.write(pvt.getEncoded());
outPvt.close();
FileOutputStream outPub = new FileOutputStream(outFile + ".pub");
outPub.write(pub.getEncoded());
outPub.close();
It gave me two files (rsa_key.key and rsa_key.pub) and then I encoded public key with Base64 so C# could read it:
PublicKeyBase64.java
String keyFile2 = "rsa_key.pub";
Path path2 = Paths.get(keyFile2);
byte[] bytes2 = Files.readAllBytes(path2);
X509EncodedKeySpec ks2 = new X509EncodedKeySpec(bytes2);
KeyFactory kf2 = KeyFactory.getInstance("RSA");
PublicKey pub = kf2.generatePublic(ks2);
Base64.Encoder encoder = Base64.getEncoder();
String outFile = "en_rsa_key";
Writer out = new FileWriter(outFile + ".pub");
out.write(encoder.encodeToString(pub.getEncoded()));
out.close();
Then I created my C# class to encrypt data
Encrypter.cs
class Encrypter
{
private const string PATH = "..\\..\\key\\en_rsa_key.pub";
private string PublicKey;
public Encrypter()
{
PublicKey = File.ReadAllText(PATH);
Console.WriteLine(PublicKey.Length);
}
public string encryptData(string dataToEncrypt)
{
Asn1Object obj = Asn1Object.FromByteArray(Convert.FromBase64String(PublicKey));
DerSequence publicKeySequence = (DerSequence)obj;
DerBitString encodedPublicKey = (DerBitString)publicKeySequence[1];
DerSequence publicKey = (DerSequence)Asn1Object.FromByteArray(encodedPublicKey.GetBytes());
DerInteger modulus = (DerInteger)publicKey[0];
DerInteger exponent = (DerInteger)publicKey[1];
RsaKeyParameters keyParameters = new RsaKeyParameters(false, modulus.PositiveValue, exponent.PositiveValue);
RSAParameters parameters = DotNetUtilities.ToRSAParameters(keyParameters);
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(parameters);
//Console.WriteLine(dataToEncrypt);
byte[] encryptedData = rsa.Encrypt(Encoding.UTF8.GetBytes(dataToEncrypt), true);
//Console.WriteLine(Convert.ToBase64String(encryptedData));
return Convert.ToBase64String(encryptedData);
}
}
And at last my Decrypter class
public class Decrypter {
private static final String PATH = "I:\\rsa_key.key";
private File privateKeyFile = new File(PATH);
private RSAPrivateKey privateKey;
public Decrypter() throws Exception {
DataInputStream dis = new DataInputStream(new FileInputStream(privateKeyFile));
byte[] privateKeyBytes = new byte[(int)privateKeyFile.length()];
dis.read(privateKeyBytes);
dis.close();
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(privateKeyBytes);
privateKey = (RSAPrivateKey) keyFactory.generatePrivate(privSpec);
}
public String decrypt(String data) throws Exception{
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
return new String(cipher.doFinal(data.getBytes()));
}
}
I send encrypted data in C# via WebSocket and receive it without any problem because data while sending and receiving is the same. The problem is that data length is 344 characters (bytes) so when I decrypt my data it shows me and error: javax.crypto.IllegalBlockSizeException: Data must not be longer than 245 bytes
I'm using 2048 bit key so it's 256B - 11B for padding that's why it's 245B. But the problem is that it always generates 344B and it doesn't depend on message to encode length. For example encoding "A" gives 344B and "Hello world" also 344B.
In this topic people say to use symmetric key, Encrypt the data with the symmetric key and then Encrypt the symmetric key with rsa. But it won't help because any encrypted string in my code has 344B size, so I won't be able to decrypt encrypted symmetric key.
What's wrong with my code?
i have generate a private key with java code and a save it so:
KeyPair keys;
try {
keys = KeyTools.genKeys("2048", AlgorithmConstants.KEYALGORITHM_RSA);
//SAVE PRIVKEY
//PrivateKey privKey = keys.getPrivate();
//byte[] privateKeyBytes = privKey.getEncoded();
PKCS10CertificationRequest pkcs10 = new PKCS10CertificationRequest("SHA256WithRSA",
CertTools.stringToBcX509Name("CN=NOUSED"), keys.getPublic(), null, keys.getPrivate());
//Save Privatekey
String privateKeyFilename = "C:/Users/l.calicchio/Downloads/privateKey.key";
String password="prismaPrivateKey";
byte[] start="-----BEGIN PRIVATE KEY-----\n".getBytes();
byte[] end="\n-----END PRIVATE KEY-----".getBytes();
byte[] privateKeyBytes = keys.getPrivate().getEncoded();
byte[] encryptedPrivateKeyBytes = passwordEncrypt(password.toCharArray(), privateKeyBytes);
File f=new File(privateKeyFilename);
if (f.exists()){
f.delete();
}
FileOutputStream fos = new FileOutputStream(f,true);
fos.write(start);
fos.write(Base64.encode(encryptedPrivateKeyBytes));
fos.write(end);
fos.close();
Now i want add passphrase to private key.
so i found this code:
private static byte[] passwordEncrypt(char[] password, byte[] plaintext) throws Exception {
String MYPBEALG = "PBEWithSHA1AndDESede";
int count = 20;// hash iteration count
SecureRandom random = new SecureRandom();
byte[] salt = new byte[8];
random.nextBytes(salt);
// Create PBE parameter set
PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, count);
PBEKeySpec pbeKeySpec = new PBEKeySpec(password);
SecretKeyFactory keyFac = SecretKeyFactory.getInstance(MYPBEALG);
SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);
Cipher pbeCipher = Cipher.getInstance(MYPBEALG);
// Initialize PBE Cipher with key and parameters
pbeCipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec);
// Encrypt the encoded Private Key with the PBE key
byte[] ciphertext = pbeCipher.doFinal(plaintext);
// Now construct PKCS #8 EncryptedPrivateKeyInfo object
AlgorithmParameters algparms = AlgorithmParameters.getInstance(MYPBEALG);
algparms.init(pbeParamSpec);
EncryptedPrivateKeyInfo encinfo = new EncryptedPrivateKeyInfo(algparms, ciphertext);
// and here we have it! a DER encoded PKCS#8 encrypted key!
return encinfo.getEncoded();
but when i use this openssl command
openssl asn1parse -in privateKey.key
i have no error, but when i try this:
openssl rsa -noout -modulus -in privatekey.it
i have a error:
unable to load private key 9964:error:0D0680A8:asn1 encoding
routines:ASN1_CHECK_TLEN:wrong tag:.\crypto\as n1\tasn_dec.c:1319:
9964:error:0D06C03A:asn1 encoding
routines:ASN1_D2I_EX_PRIMITIVE:nested asn1 err
or:.\crypto\asn1\tasn_dec.c:831: 9964:error:0D08303A:asn1 encoding
routines:ASN1_TEMPLATE_NOEXP_D2I:nested asn1 e
rror:.\crypto\asn1\tasn_dec.c:751:Field=version,
Type=PKCS8_PRIV_KEY_INFO 9964:error:0907B00D:PEM
routines:PEM_READ_BIO_PRIVATEKEY:ASN1 lib:.\crypto\pem\p
em_pkey.c:132:
I think that the private key is missing the following line:
Proc-Type: 4,ENCRYPTED
"DEK-Info: " + "AES-256-CBC"......
but how i add this(where i get this information?)?
tnx
Please read the manual, man rsa gives the following details:
Note this command uses
the traditional SSLeay compatible format for private key encryption:
newer applications should use the more secure PKCS#8 format using the
pkcs8 utility.
My knowledge of encryption is very basic, so apologies for any ignorance on my part.
Within an Android app I am currently trying to mimic the execution of this command using the SpongyCastle library and standard java.security libs:
echo 'test' | openssl rsautl -encrypt -pubin -inkey test.pub | base64 > encrypted_file
It should be noted that the reading/writing to and from files in the command are not going to be implemented and I have my public key (i.e. test.pub) as a Base64 encoded string base64key in my code.
I have attempted the following but am certain it does not work:
static {
Security.insertProviderAt(new BouncyCastleProvider(), 1);
}
//...more code here
byte[] pka = Base64.decode(base64key);
X509EncodedKeySpec x = new X509EncodedKeySpec(pka);
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(x);
byte[] testToByte = "test".getBytes("UTF8");
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] cipherText = cipher.doFinal(testToByte);
String encrypted = Base64.encode((new String(cipherText, "UTF8").toString().getBytes()))
I know this is way off, but am not sure where to turn. Any help would be appreciated.
This was eventually solved using the following methods:
private void stripHeaders(){
public_key = public_key.replace("-----BEGIN PUBLIC KEY-----", "");
public_key = public_key.replace("-----END PUBLIC KEY-----", "");
}
public byte[] encryptWithPublicKey(String encrypt) throws Exception {
byte[] message = encrypt.getBytes("UTF-8");
stripHeaders();
PublicKey apiPublicKey= getRSAPublicKeyFromString();
Cipher rsaCipher = Cipher.getInstance("RSA/None/PKCS1Padding", "SC");
rsaCipher.init(Cipher.ENCRYPT_MODE, apiPublicKey);
return rsaCipher.doFinal(message);
}
private PublicKey getRSAPublicKeyFromString() throws Exception{
KeyFactory keyFactory = KeyFactory.getInstance("RSA", "SC");
byte[] publicKeyBytes = Base64.decode(public_key.getBytes("UTF-8"), Base64.DEFAULT);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(publicKeyBytes);
return keyFactory.generatePublic(x509KeySpec);
}
I have generated a private key and encrypted it with a password. Now I want to load it to EncryptedPrivateKeyInfo, so that I can construct and export it into a PEM format file. Below is the code I use,
final CertAndKeyGen keypair = new CertAndKeyGen("RSA", "SHA1WithRSA", null);
final X500Name x500Name =
new X500Name("IN", "AP", "HYD", "TEST", "TEST_ORG", "test#xyz.com");
keypair.generate(1024);
final PrivateKey privKey = keypair.getPrivateKey();
final X509Certificate[] chain = new X509Certificate[1];
long validity = 123;
chain[0] = keypair.getSelfCertificate(x500Name, new Date(),
validity * 24 * 60 * 60);
Key key = new SecretKeySpec(password.getBytes(), ALGO);
Cipher c = Cipher.getInstance(ALGO);
c.init(Cipher.ENCRYPT_MODE, key);
byte[] encVal = c.doFinal(privKey.getEncoded());
AlgorithmParameters params = AlgorithmParameters.getInstance("DES");
params.init(encVal); // <--- exception thrown here
EncryptedPrivateKeyInfo encinfo = new EncryptedPrivateKeyInfo(params, encVal);
// displaying encrypting value
String encryptedValue = Base64.encodeBase64String(encinfo.getEncoded());
System.out.println(encryptedValue);
But while executing the above code, I get the below exception
java.io.IOException: DER input not an octet string
at sun.security.util.DerInputStream.getOctetString(DerInputStream.java:233)
at com.sun.crypto.provider.SunJCE_t.a(DashoA13*..)
at com.sun.crypto.provider.DESParameters.engineInit(DashoA13*..)
at java.security.AlgorithmParameters.init(AlgorithmParameters.java:260)
in the line params.init(encVal);. I don't get what's going wrong from the exception. Any help or suggestion will be really appreciated. Thanks in advance.
A PKCS #8 file containing an encrypted private key has the following ASN.1 structure:
EncryptedPrivateKeyInfo ::= SEQUENCE {
encryptionAlgorithm EncryptionAlgorithmIdentifier,
encryptedData EncryptedData }
Notice how the encrypted data is only part of the total data package. You need to include this data in a sequence along with the encryption algorithm identifier. Your code above produces only the encryptedData part.
I would suggest you consider using BouncyCastle. It looks like they may have improved support for PKCS#8 recently. Here's some sample code:
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
KeyPair keyPair = generator.generateKeyPair();
final PrivateKey privKey = keyPair.getPrivate();
JceOpenSSLPKCS8EncryptorBuilder builder =
new JceOpenSSLPKCS8EncryptorBuilder(PKCS8Generator.PBE_SHA1_3DES);
builder.setIterationCount(10000);
builder.setPasssword("Hello, World!".toCharArray());
OutputEncryptor outputEncryptor = builder.build();
PKCS8Generator pkcs8Generator =
new JcaPKCS8Generator(privKey, outputEncryptor);
try (PemWriter writer = new PemWriter(new PrintWriter(System.out))) {
writer.writeObject(pkcs8Generator);
}
This will output an encrypted private key:
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIICrjAoBgoqhkiG9w0BDAEDMBoEFM1kXgdc0vzEhCwQG5G2wAaSA7POAgInEASC
AoAkBPjKkNVpt9O9+Q63WMscX0eEiGyD6/kFwI+ZgH4/s98uKDHxWTB0fQ+HA+Iy
gQC1b+QhT2HVR0DIB6lfhou4FrcJXBAqf4h0ybkfmE4xTfVQcCpgKm4uUC/FYjks
dgTMajN9NHL06nUjd/0uVsM2bzbJHXlDmPRB0LdfvuxzcGN0Vvn13IJrjRnGwTGR
nv6ZyE3gbjCRanINhMtCDMtg9Ydm7+DHC53YOeqbbhgO9/aJHpRzX/H6zLYp/oa0
GpHM6NzsTfABS8QyqR+EaoFad/XHvC9nKUDqm8LVjmKdlXrLDmpHQ4kxZqMIbijE
/Pu2IfHB0jYYa93F2i0fKkIaeve4oo3/izBn0amXVnsPMnkyrIoUUaLzI/gb/419
vp+1kmAKsEFCRIDQlDCiR9CyuePOaR0d7EckVMDU0uR9MXYAV73Y3VRXe1votrQ/
5Wi8ueio9TBDYj9wYYoYmRpz454HqJ/71k0xr5IJugJIJkUzNN9VkjK9rhgT0Vz5
wY/E1yZZepqCR1xrYgDuu/n4t63EERMo+BgkpKBMnWliU3QdQT2r4qBnma+c9lq8
IWb3y3Phl4LsX3DnLed1rUIOcQKiN2T9/yl+7eEtk8APkKuLK5DDDytgAcOQ1MIF
7Ie2939OG1c3mAwrdeOk20zf0SSbpX17MDmUoMwKsP9u8bqHahNoucjtuVSRtjSe
94xntc0fHkiIueApffxjErJOd2pmjGxJ7D2lkjV4G3AEg3vqFXk6E8nPIqIZL4qI
9KzmAFv88+QDIVTM5fheIOxZpeBkXtq2e19gCijiysqLDWL2CLuN4LVzhKCkJF6x
QGN1FcLk95ikvhI/LEn6qrih
-----END ENCRYPTED PRIVATE KEY-----
I have to read pem key files to get RSA Public key,and then use them to encrypt.
I can do this using openssl and convert pem file to der file.
and then load my key using X509EncodedKeySpec and PKCS8EncodedKeySpec.
But I don't want to do this because pem is the user key exchange format.
user can register it's own key can like this :
--BEGIN PUBLIC KEY-- MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgGi0/vKrSIIQMOm4atiw+2s8tSojOKHsWJU3oPTm
b1a5UQIH7CM3NgtLvUF5DqhsP2jTqgYSsZSl+W2RtqCFTavZTWvmc0UsuK8tTzvnCXETsnpjeL13
Hul9JIpxZVej7b6KxgyxFAhuz2AGscvCXnepElkVh7oGOqkUKL7gZSD7AgMBAAE=
--END PUBLIC KEY--
and this key is store in a database in this format...
Here is the code I have tried..
File pubKeyFile=new File("D:/public_key.pem");
DataInputStream dis = new DataInputStream(new FileInputStream(pubKeyFile));
byte[] pubKeyBytes = new byte[(int)pubKeyFile.length()];
dis.readFully(pubKeyBytes);
dis.close();
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(pubKeyBytes);
RSAPublicKey pubKey = (RSAPublicKey) keyFactory.generatePublic(pubSpec);
I am getting exception as
java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: invalid key format
As I am completely new to encryption concepts can anyone please help me to solve this exception?
Many thanks.
With bouncycastle, it would be done this way:
CertificateFactory cf = CertificateFactory.getInstance("X509", "BC");
InputStream is = new FileInputStream("D:/public_key.pem");
X509Certificate certificate = (X509Certificate) cf.generateCertificate(is);
is.close();
RSAPublicKey pubKey = (RSAPublicKey)certificate.getPublicKey();
You were almost there, with the standard provider. You just need to strip the header and footer lines:
List<String> lines = Files.readAllLines(Paths.get(path), StandardCharsets.US_ASCII);
if (lines.size() < 2)
throw new IllegalArgumentException("Insufficient input");
if (!lines.remove(0).startsWith("--"))
throw new IllegalArgumentException("Expected header");
if (!lines.remove(lines.size() - 1).startsWith("--"))
throw new IllegalArgumentException("Expected footer");
byte[] raw = Base64.getDecoder().decode(String.join("", lines));
KeyFactory factory = KeyFactory.getInstance("RSA");
PublicKey pub = factory.generatePublic(new X509EncodedKeySpec(raw));
try using bouncycastele's PemReader .
PublicKey getPublicKey(String pubKeyStr) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
PemObject pem = new PemReader(new StringReader(pubKeyStr)).readPemObject();
byte[] pubKeyBytes = pem.getContent();
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(pubKeyBytes);
RSAPublicKey pubKey = (RSAPublicKey) keyFactory.generatePublic(pubSpec);
return pubKey;
}