I want to save my private key in json file ( hex format ) then read it as PrivateKey.
Here Keys generate function
public void generateKeyPair() {
try {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ECDSA", "BC");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256k1");
keyGen.initialize(ecSpec,random);
KeyPair keyPair = keyGen.generateKeyPair();
privateKey = keyPair.getPrivate();
publicKey = keyPair.getPublic();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
This is how i save it into json file
a.generateKeyPair();
byte[] enc_key = a.privateKey.getEncoded();
StringBuilder key_builder = new StringBuilder();
for(byte b : enc_key){
key_builder.append(String.format( "%02X",b));
}
String serialized_key = key_builder.toString();
account.privateKey=serialized_key;
try (Writer writer = new FileWriter("Output.json")) {
Gson gson = new GsonBuilder().create();
gson.toJson(account, writer);
} catch (IOException e) {
e.printStackTrace();
}
And read it from file
Gson gson = new GsonBuilder().create();
try (Reader read1 = new FileReader("Output.json")) {
account=gson.fromJson(read1,account.getClass());
byte[] encoded_key=account.privateKey.getBytes();
a.privateKey = getPrivateKey(encoded_key);
public static PrivateKey getPrivateKey(byte[] privkey) throws NoSuchAlgorithmException, InvalidKeySpecException {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privkey);
KeyFactory kf = null;
try {
kf = KeyFactory.getInstance("ECDSA", "BC");
} catch (NoSuchProviderException e) {
e.printStackTrace();
}
PrivateKey privateKey = kf.generatePrivate(privateKeySpec);
return privateKey;
}
Function fail and i get error
java.security.spec.InvalidKeySpecException: encoded key spec not recognized: failed to construct sequence from byte[]: unknown tag 19 encountered
You are forgetting to hex decode the private key. Just performing getBytes won't do that.
The encoded byte starts with a SEQUENCE, tag 0x30. This in hex will of course be "30" or, in ASCII: 0x33, 0x30: these are the two first bytes returned by getBytes. Now the decoder looks at the first byte with bit value 0b001_10011. The last 5 bits encode the tag value, which is 16 + 2 + 1 = 19. Hence the specific error.
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 am trying to use GCM Mode for encryption and decryption. Unfortunately decryption doesn't work.
Do I have to use the same initialization vector for both encryption and decryption classes? I already tried that, unsuccessfully...
Could the random argument in keyGen.init(128, random) be the problem?
Encryption code:
public class AES128SymmetricEncryption {
private static final int GCM_NONCE_LENGTH = 12; // in bytes
private static final int GCM_TAG_LENGTH = 16; // in bytes
public static void encode (FileInputStream ciphertextSource, FileOutputStream plaintextDestination)
{
try {
int numRead;
SecureRandom random = SecureRandom.getInstanceStrong();
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128, random);
SecretKey key = keyGen.generateKey();
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, getIV(random));
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
byte[] buf = new byte[2048];
while ((numRead = ciphertextSource.read(buf)) > 0) {
byte[] decryptedBlock = cipher.update(buf, 0, numRead);
plaintextDestination.write(decryptedBlock);
}
}
catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (plaintextDestination != null) {
ciphertextSource.close();
}
if (plaintextDestination != null) {
plaintextDestination.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static byte[] getIV(SecureRandom random) {
final byte[] nonce = new byte[GCM_NONCE_LENGTH];
random.nextBytes(nonce);
System.out.println(nonce);
return nonce;
}
public static void main(String[] args) throws GeneralSecurityException, IOException
{
Security.addProvider(new BouncyCastleProvider());
FileInputStream fis = new FileInputStream("C:/Users/roehrlef/Desktop/Test Data/Source Data/100KB.jpg");
FileOutputStream fos = new FileOutputStream("C:/Users/roehrlef/Desktop/Test Data/Encrypted Data/encrypted.jpg");
encode(fis, fos);
}
}
Decryption code:
public class AES128SymmetricDecryption {
private static final int GCM_NONCE_LENGTH = 12; // in bytes
private static final int GCM_TAG_LENGTH = 16; // in bytes
public static void decode (FileInputStream ciphertextSource, FileOutputStream plaintextDestination)
{
try {
int numRead = 0;
SecureRandom random = SecureRandom.getInstanceStrong();
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128, random);
SecretKey key = keyGen.generateKey();
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, getIV(random));
cipher.init(Cipher.DECRYPT_MODE, key, spec);
CipherInputStream cis = new CipherInputStream(ciphertextSource, cipher);
byte[] buf = new byte[2048];
while ((numRead = cis.read(buf)) > 0) {
byte[] decryptedBlock = cipher.update(buf, 0, numRead);
plaintextDestination.write(decryptedBlock);
}
}
catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (plaintextDestination != null) {
ciphertextSource.close();
}
if (plaintextDestination != null) {
plaintextDestination.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static byte[] getIV(SecureRandom random) {
final byte[] nonce = new byte[GCM_NONCE_LENGTH];
random.nextBytes(nonce);
System.out.println(nonce);
return nonce;
}
public static void main(String[] args) throws GeneralSecurityException, IOException
{
Security.addProvider(new BouncyCastleProvider());
FileInputStream fis = new FileInputStream("C:/Users/roehrlef/Desktop/Test Data/Encrypted Data/encrypted.jpg");
FileOutputStream fos = new FileOutputStream("C:/Users/roehrlef/Desktop/Test Data/Decrypted Data/decrypted.jpg");
decode(fis, fos);
}
}
You're using KeyGenerator twice; once for encryption and once for decryption. This class generates a new random key. With symmetric ciphers you need to use the same key for encryption and decryption (hence the name).
In general you should use the following classes for the following purposes:
For symmetric keys (e.g. AES, HMAC):
KeyGenerator: brand new secret (symmetric) keys;
SecretKeyFactory: decoding secret (symmetric) keys, for instance generated by the method Key#getEncoded() implemented by most key classes;
And for asymmetric public / private key pairs (e.g. RSA):
KeyPairGenerator: brand new public / private asymmetric key pairs;
KeyFactory: decoding public / private (asymmetric) keys from a stored key format, for instance generated by the method Key#getEncoded() implemented by most key classes;
Both symmetric and asymmetric keys may be stored in key stores:
KeyStore: storing keys / certificates in a key container such as PKCS#12 key stores;
Finally there are some other options for creating keys:
KeyAgreement: establishing a key by a key agreement function such as Diffie-Hellman key exchange;
Cipher#unwrap: unwrapping (decrypting) keys created using Cipher#wrap (or a similar function on another platform) with another key.
You should probably either store and retrieve the key in a KeyStore - which you can load / save to file. Note that not all key stores are created equal; Java 9 expanded the functionality of PKCS#12 key stores and made them the default. You code also encode the key and use a SecretKeyFactory to decode it again.
Or you can just cheat and reuse the SecretKey instance you generated during encryption, and implement key storage later. That would be good for testing purposes. In the end you need to share the key for symmetric encryption.
And yes, the IV needs to be identical on both sides. Usually it is just stored in front of the ciphertext. The IV should be unique for each encryption, so you have to use the random number generator over there.
I am trying to generate public key and signature in readable format which can be used in rest service .i have write following code .
public static void GenearetSignature(String url,String signatureValue,String publicKey){
KeyPairGenerator keyGen;
try {
keyGen = KeyPairGenerator.getInstance("DSA", "SUN");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG", "SUN");
keyGen.initialize(1024, random);
KeyPair pair = keyGen.generateKeyPair();
PrivateKey priv = pair.getPrivate();
PublicKey pub = pair.getPublic();
System.out.println("Priavte key"+priv);
System.out.println("Public key");
Signature dsa = Signature.getInstance("SHA1withDSA", "SUN");
dsa.initSign(priv);
dsa.update(url.getBytes());
byte[] signatureValueArray = dsa.sign();
signatureValue=new String(signatureValueArray,"UTF8");
byte[] publicKeyArray=pub.getEncoded();
publicKey = new String(publicKeyArray);
System.out.println("publicKey is "+publicKey);
System.out.println("signatureValue is "+signatureValue);
} catch (NoSuchAlgorithmException ex) {
// TODO Auto-generated catch block
ex.printStackTrace();
} catch(Exception e){
e.printStackTrace();
}
}
But this is not generating in alphanumeric value .how can i achieve that .please help me
If you want hexadecimal represenation of byte array use
javax.xml.bind.DatatypeConverter.printHexBinary(byte[] array)
You can use base64 for communicating over rest. Another example is HTTP Basic authentication that encode password using base64 to putting it in HTTP header
I have a function that successfully reads a openssl formatted private key:
static AsymmetricKeyParameter readPrivateKey(string privateKeyFileName)
{
AsymmetricCipherKeyPair keyPair;
using (var reader = File.OpenText(privateKeyFileName))
keyPair = (AsymmetricCipherKeyPair)new PemReader(reader).ReadObject();
return keyPair.Private;
}
and returns an AsymmetricKeyParameter which is then used to decrypt an encrypted text.
Below is the decrypt code:
public static byte[] Decrypt3(byte[] data, string pemFilename)
{
string result = "";
try {
AsymmetricKeyParameter key = readPrivateKey(pemFilename);
RsaEngine e = new RsaEngine();
e.Init(false, key);
//byte[] cipheredBytes = GetBytes(encryptedMsg);
//Debug.Log (encryptedMsg);
byte[] cipheredBytes = e.ProcessBlock(data, 0, data.Length);
//result = Encoding.UTF8.GetString(cipheredBytes);
//return result;
return cipheredBytes;
} catch (Exception e) {
Debug.Log ("Exception in Decrypt3: " + e.Message);
return GetBytes(e.Message);
}
}
These work in C# using bouncy castle library and I get the correct decrypted text. However, when I added this to Java, the PEMParser.readObject() returns an object of type PEMKeyPair instead of AsymmetricCipherKeyPair and java throws an exception trying to cast it. I checked in C# and it is actually returning AsymmetricCipherKeyPair.
I don't know why Java behaves differently but I hope someone here can help how to cast this object or read the privatekey file and decrypt successfully. I used the same public and privatekey files in both C# and Java code so I don't think the error is from them.
Here for reference the Java version of how I'm reading the privatekey:
public static String readPrivateKey3(String pemFilename) throws FileNotFoundException, IOException
{
AsymmetricCipherKeyPair keyParam = null;
AsymmetricKeyParameter keyPair = null;
PEMKeyPair kp = null;
//PrivateKeyInfo pi = null;
try {
//var fileStream = System.IO.File.OpenText(pemFilename);
String absolutePath = "";
absolutePath = Encryption.class.getProtectionDomain().getCodeSource().getLocation().getPath();
absolutePath = absolutePath.substring(0, (absolutePath.lastIndexOf("/")+1));
String filePath = "";
filePath = absolutePath + pemFilename;
File f = new File(filePath);
//return filePath;
FileReader fileReader = new FileReader(f);
PEMParser r = new PEMParser(fileReader);
keyParam = (AsymmetricCipherKeyPair) r.readObject();
return keyParam.toString();
}
catch (Exception e) {
return "hello: " + e.getMessage() + e.getLocalizedMessage() + e.toString();
//return e.toString();
//return pi;
}
}
The Java code has been updated to a new API, which is yet to be ported across to C#. You could try the equivalent (but now deprecated) Java PEMReader class. It will return a JCE KeyPair though (part of the reason for the change was because the original version worked only with JCE types, not BC lightweight classes).
If using PEMParser, and you get back a PEMKeyPair, you can use org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter.getKeyPair to get a JCE KeyPair from it. Ideally there would be a BCPEMKeyConverter, but it doesn't appear to have been written yet. In any case, it should be easy to make an AsymmetricCipherKeyPair:
PEMKeyPair kp = ...;
AsymmetricKeyParameter privKey = PrivateKeyFactory.createKey(kp.getPrivateKeyInfo());
AsymmetricKeyParameter pubKey = PublicKeyFactory.createKey(kp.getPublicKeyInfo());
new AsymmetricCipherKeyPair(pubKey, privKey);
Those factory classes are in the org.bouncycastle.crypto.util package.
I'm writing a program that uses RSA for various tasks.
I know how to generate and write the key pair to file, but I cannot load the encrypted (AES-256-CFB) key pair to a KeyPair object.
So the question is: how do I load/decrypt an encrypted PEM key pair as a java.security.KeyPair object using the BouncyCastle library?
Thanks.
Generation/export code:
public void generateKeyPair(int keysize, File publicKeyFile, File privateKeyFile, String passphrase) throws FileNotFoundException, IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
SecureRandom random = new SecureRandom();
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "BC");
generator.initialize(keysize, random);
KeyPair pair = generator.generateKeyPair();
Key pubKey = pair.getPublic();
PEMWriter pubWriter = new PEMWriter(new FileWriter(publicKeyFile));
pubWriter.writeObject(pubKey);
pubWriter.close();
PEMWriter privWriter = new PEMWriter(new FileWriter(privateKeyFile));
if (passphrase == null) {
privWriter.writeObject(pair);
} else {
PEMEncryptor penc = (new JcePEMEncryptorBuilder("AES-256-CFB"))
.build(passphrase.toCharArray());
privWriter.writeObject(pair, penc);
}
privWriter.close();
}
I am assuming that you have set BouncyCastle as the security provider, for example with:
Security.addProvider(new BouncyCastleProvider());
The code you provided creates two key files, one for the private key and one for the public key. However, the public key is implicitly contained in the private key, so we only have to read the private key file to reconstruct the key pair.
The main steps then are:
Creating a PEMParser to read from the key file.
Create a JcePEMDecryptorProvider with the passphrase required to decrypt the key.
Create a JcaPEMKeyConverter to convert the decrypted key to a KeyPair.
KeyPair loadEncryptedKeyPair(File privateKeyFile, String passphrase)
throws FileNotFoundException, IOException {
FileReader reader = new FileReader(privateKeyFile);
PEMParser parser = new PEMParser(reader);
Object o = parser.readObject();
if (o == null) {
throw new IllegalArgumentException(
"Failed to read PEM object from file!");
}
JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
if (o instanceof PEMKeyPair) {
PEMKeyPair keyPair = (PEMKeyPair)o;
return converter.getKeyPair(keyPair);
}
if (o instanceof PEMEncryptedKeyPair) {
PEMEncryptedKeyPair encryptedKeyPair = (PEMEncryptedKeyPair)o;
PEMDecryptorProvider decryptor =
new JcePEMDecryptorProviderBuilder().build(passphrase.toCharArray());
return converter.getKeyPair(encryptedKeyPair.decryptKeyPair(decryptor));
}
throw new IllegalArgumentException("Invalid object type: " + o.getClass());
}
Example usage:
File privKeyFile = new File("priv.pem");
String passphrase = "abc";
try {
KeyPair keyPair = loadEncryptedKeyPair(privKeyFile, passphrase);
} catch (IOException ex) {
System.err.println(ex);
}
Reference: BouncyCastle unit test for key parsing (link).