I want to achieve end to end encryption in my app and I want my Public key to be a string so that I can send it to server. Kindly tell me a way to convert public Key to string and vice versa.
Your main question seems to be "a java code to convert public key to string and vice versa" and this can be done with a few lines of code.
The full example code generates a RSA key pair, get the encoded form of the public key (as byte array) and encodes this in
Base64 encoding to a string. The encoding is done with the Android util class. Then the string needs to be transported to the
server, decoded to a byte array and this byte array runs into a key factory that "regenerates" the public key - voila.
Kindly note that the code has no exception handling and is for educational purpose only.
output:
Convert RSA public key into a string an dvice versa
publicKey: Sun RSA public key, 2048 bits
params: null
modulus: 18853651626448533042344052742185586831509096183921137436644620443732807152716528158465416708071104899767862289783079092216042499687784322092232163872332358586822678596223733228124113017356896219191227134298362353552882770945818159114272146532048929436504145362418430766823867890113522564795700689158702507402243560009550536419065620409534494384621580364502393563063483223294632627903706549112325066113361455750410642281763368591922729105346933211850575970566025026523917327761707615319008741255611490792106558703015066844972642677443110535667601315009551275389632601989979561472926080344790824117481932026867279062677
public exponent: 65537
publicKeyString: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlVmFPS+dIDcJILVZMM9hdQwEiLcHx7SVYF5gOrakPH7ZzilcOjWYcR47qktQAUu97JbLu3h3WPmm1nkgSXU1lVBoqc8pA1BHmzdvMK61A/F77nspDce0QqH5f5kQvYcuQrl+cCUvaTI/3/WBWwRIO2xGKMKRIgiBWDN/HVsqYU2O2pAJnLKQbz9NkkfGNVdzn4H21hi0shCVWCpt80zZkn0gm3oWtCGHOnyszXUOiw7inAdGkNGiZRyiFOUmFNRKLIYM3WiyU1NRGVrjto9NH/E53JdgSyBEu7kkWMLJqNuwj+DNQFu3Qq5VrNxwWggrwhFG+K0y0+Ed+scT003mlQIDAQAB
publicKeyServer: Sun RSA public key, 2048 bits
params: null
modulus: 18853651626448533042344052742185586831509096183921137436644620443732807152716528158465416708071104899767862289783079092216042499687784322092232163872332358586822678596223733228124113017356896219191227134298362353552882770945818159114272146532048929436504145362418430766823867890113522564795700689158702507402243560009550536419065620409534494384621580364502393563063483223294632627903706549112325066113361455750410642281763368591922729105346933211850575970566025026523917327761707615319008741255611490792106558703015066844972642677443110535667601315009551275389632601989979561472926080344790824117481932026867279062677
public exponent: 65537
code:
import android.util.Base64;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
public class Main {
public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException {
System.out.println("Convert RSA public key into a string an dvice versa");
// generate a RSA key pair
KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA");
keygen.initialize(2048, new SecureRandom());
KeyPair keyPair = keygen.generateKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
System.out.println("publicKey: " + publicKey);
// get encoded form (byte array)
byte[] publicKeyByte = publicKey.getEncoded();
// Base64 encoded string
String publicKeyString = Base64.encodeToString(publicKeyByte, Base64.NO_WRAP);
System.out.println("publicKeyString: " + publicKeyString);
// ... transport to server
// Base64 decoding to byte array
byte[] publicKeyByteServer = Base64.decode(publicKeyString, Base64.NO_WRAP);
// generate the publicKey
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKeyServer = (PublicKey) keyFactory.generatePublic(new X509EncodedKeySpec(publicKeyByteServer));
System.out.println("publicKeyServer: " + publicKeyServer);
}
}
Related
I am supposed to receive server public key ("ECDH" , "secp256k1") in HEX format, which is uncompressed (65 bytes), generate my own public key in Android which is in X.509format (88 bytes), and then generate a shared secret which must be 32 bytes.
Now when I want to get server public key I ran into this error:
java.security.spec.InvalidKeySpecException: invalid KeySpec: point not on curve
The procedure:
First I produce my own public key, then turn server HEX key into byte array: serverKey.getBytes() , then put it in another method below :
Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
KeyPairGenerator kpgen =KeyPairGenerator.getInstance("ECDH", "BC");
ECGenParameterSpec genspec = new ECGenParameterSpec("secp256k1");
kpgen.initialize(genspec);
KeyPair localKeyPair = kpgen.generateKeyPair();
ECPublicKey remoteKey = decodeECPublicKey(serverKey.getBytes());
KeyAgreement localKA = KeyAgreement.getInstance("ECDH");
localKA.init(keyPair.getPrivate());
localKA.doPhase((ECPublicKey) remoteKey, true);
byte[] localSecret = localKA.generateSecret();
decodeECPublicKey is:
public static decodeECPublicKey getPublicKeyFromBytes(byte[] pubKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
KeyFactory kf = KeyFactory.getInstance("ECDH", new BouncyCastleProvider());
ECNamedCurveSpec params = new ECNamedCurveSpec("secp256k1", spec.getCurve(), spec.getG(), spec.getN());
ECPoint point = ECPointUtil.decodePoint(params.getCurve(), pubKey);
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, params);
ECPublicKey pk = (ECPublicKey) kf.generatePublic(pubKeySpec);
return pk;
}
Which upon execution, produces this error:
java.lang.IllegalArgumentException: Invalid point encoding 0x30
What am I doing wrong?
EDIT:
Ok. The wrong part was serverKey.getBytes() thanks to #Topaco. Now that I have localSecret I want to encrypt a String with AES-256-CBC algorithm using first 16 bytes of localSecret as iv, and second ones as the key.
I have written this code but when I send the result to the server it generates error:
public static byte[] enc(byte[] key, String toBeEnc) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, UnsupportedEncodingException, BadPaddingException, IllegalBlockSizeException {
Cipher ecipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = new byte[16];
System.arraycopy(key, 0, iv, 0, iv.length);
byte[] keyByte = new byte[16];
System.arraycopy(key, 16, keyByte, 0, keyByte.length);
Key keyF = new SecretKeySpec(keyByte, "AES");
ecipher.init(Cipher.ENCRYPT_MODE, keyF, new IvParameterSpec(iv));
byte[] enc = ecipher.doFinal(toBeEnc.getBytes(StandardCharsets.UTF_8));
return enc;
}
Because many people viewed this question, I'm gonna answer it here: Just make yourself comfortable and use OpenSSL library for Android. Our backend used it to generate its key, I used it as well and it works like a charm.
I want to mail encrypted log file from my app. Since logs can be larger I have encrypted data using AES and encrypted the key using RSA. Since the AES key is required to decrypt the log, I am sending the encrypted key and logs in the same file.
Question 1: Is this right approach ? If not what is the best approach to follow in this scenario.Below is the code for the same.
public static String encrypt(String data) {
StringBuilder encryptedData = new StringBuilder();
try {
// Generate AES key.
KeyGenerator generator = KeyGenerator.getInstance("AES");
// The AES key size in number of bits.
generator.init(256);
SecretKey secKey = generator.generateKey();
// Initialize AES Cipher, IV and encrypt string.
Cipher aesCipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
aesCipher.init(Cipher.ENCRYPT_MODE, secKey, new IvParameterSpec(new byte[16]));
byte[] byteCipherText = aesCipher.doFinal(data.getBytes());
String encryptedText = Base64.encodeToString(byteCipherText, Base64.DEFAULT);
// Initialize RSA Cipher and generate public key.
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(Base64.decode(PUBLIC_KEY, Base64.DEFAULT));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey puKey = keyFactory.generatePublic(publicSpec);
cipher.init(Cipher.PUBLIC_KEY, puKey);
// Encrypt key and text.
byte[] encryptedKey = cipher.doFinal(secKey.getEncoded());
String aesKey = Base64.encodeToString(encryptedKey, Base64.DEFAULT);
encryptedData.append(aesKey);
encryptedData.append(encryptedText);
} catch (Exception e) {
e.printStackTrace();
}
return encryptedData.toString();
}
Since the AES key is required to decrypt the log, I am sending the encrypted key and logs in the same file.
Question 1: Is this right approach ? If not what is the best approach to follow in this scenario.Below is the code for the same.
The approach is correct, what I'm missing is authentication (HMAC, GCM, ...).
There are some standards how to bundle the encrypted key, content and authentication together (e.g. CMS, PKCS7, OpenPGP, ..) however if it's for your own application, you may do it your way (don't bother with standards).
If you want to use RSA use RSA-KEM
Well, using RSA KEM you may save a little of performance skipping the padding, but I'd try if it is feasible for you. As well there's an issue when encrypting the same key material with different public keys.
I'd keep it simple - just use the properly padded RSA encryption.
I'd suggest to use OAEP padding RSA/ECB/OAEPWithSHA-256AndMGF1Padding instead of PKCS1Padding (OAEP is considered newer/safer)
I'm trying to encrypt data using a given public key.
public static final String public_key = "MIIBCgKCAQEAr/oYAoxIcXnLzVDNN6TPJVjkwOJZnDcSEeoRntqhOvgjiycfswMWZZ5+UClJ4CMgMCVAs71BzAJzPv902Jt763SPkAO/vh6CwfLq2S3YcqDoRQJYZuSKQHW40R6sN7eFvQdxYhJnF45ketCdLdPFuF5o/ieChwLcCEDKzkWD7xio2TQlZ8jfzB4jNGr6bmW/aqF5ihe0pbhtfvlyM+jNF2vWeB1SCJ4v5zHLNKKYNy4cMsmIGHKB+0BaGVz87eYp65FFc2K9LawBBbWtVCxykYBzEnXRuU+0YzcTi4LThXg1cUsf++LK9qL/G7PZdN6HMGP7DYzgstFLfp8VRpKhqQIDAQAB";
String encryptData(String txt)
{
String encoded = null;
try {
PublicKey key = KeyFactory.getInstance("RSA").generatePublic(
new X509EncodedKeySpec(Base64.decode(public_key, Base64.DEFAULT)));
Cipher cph = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cph.init(Cipher.ENCRYPT_MODE, key);
encoded = Base64.encodeToString(cph.doFinal(txt.getBytes()),
Base64.DEFAULT);
}
catch (Exception e) {
e.printStackTrace();
}
return encoded;
}
And get the error
W/System.err: java.security.spec.InvalidKeySpecException:
java.lang.RuntimeException: error:0D0680A8:asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag
at com.android.org.conscrypt.OpenSSLKey.getPublicKey(OpenSSLKey.java:143)
at com.android.org.conscrypt.OpenSSLRSAKeyFactory.engineGeneratePublic(OpenSSLRSAKeyFactory.java:47)
at java.security.KeyFactory.generatePublic(KeyFactory.java:172)
I have used the same public key in Python3, with the 'BEGIN PUBLIC KEY...END PUBLIC KEY' header/footer and it works fine:
public_key = """-----BEGIN PUBLIC KEY-----
MIIBCgKCAQEAr/oYAoxIcXnLzVDNN6TPJVjkwOJZnDcSEeoRntqhOvgjiycfswMWZZ5+UClJ4CMgMCVAs71BzAJzPv902Jt763SPkAO/vh6CwfLq2S3YcqDoRQJYZuSKQHW40R6sN7eFvQdxYhJnF45ketCdLdPFuF5o/ieChwLcCEDKzkWD7xio2TQlZ8jfzB4jNGr6bmW/aqF5ihe0pbhtfvlyM+jNF2vWeB1SCJ4v5zHLNKKYNy4cMsmIGHKB+0BaGVz87eYp65FFc2K9LawBBbWtVCxykYBzEnXRuU+0YzcTi4LThXg1cUsf++LK9qL/G7PZdN6HMGP7DYzgstFLfp8VRpKhqQIDAQAB
-----END PUBLIC KEY-----
"""
def encode(msg):
rsa_key = RSA.importKey(public_key)
pks1_v1_5 = PKCS1_v1_5.new(rsa_key)
encrypted = pks1_v1_5.encrypt(msg.encode('utf-8'))
encrypted = base64.b64encode(encrypted)
return encrypted
Can someone help me out plz?
--- EDIT ---
I did some debugging on the Python code: stepping into 'RSA.importKey(public_key)' I see it recognizes the key as PEM encoded key, removes the header/footer and converts it to binary (binascii.a2b_base64). The binary is passed to RSA._importKeyDER which discovers that it follows the PKCS#1 standard and, in comment, 'The DER object is an RSAPublicKey SEQUENCE with two elements'.
Are you generate your public key from openssl, since your public key is too long:
for example i generated from openssl and replace your public key, and everything fine:
openssl genrsa -out key.pem 1024
openssl rsa -in key.pem -pubout > key.pub
and paste key.pub string in your code.
I'm using RSA encrypt text and decrypt text. The public key and the private key are generated with openssl tool.
I encountered an "java.lang.ArrayIndexOutOfBoundsException: too much data for RSA block" exception when decrypting data.
Here is the RSA util class:
package studio.uphie.app;
import android.util.Base64;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
/**
* Created by Uphie on 2016/4/11.
*/
public class RSA {
private static String RSA = "RSA";
/**
*
* #param text text to be encrypted
* #param pub_key rsa public key
* #return encrypted data in byte-array form
*/
public static byte[] encryptData(String text, String pub_key) {
try {
byte[] data = text.getBytes();
PublicKey publicKey = getPublicKey(Base64.decode(pub_key.getBytes(), Base64.DEFAULT));
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(data);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
*
* #param text text to be decrypted
* #param pri_key rsa private key
* #return
*/
public static byte[] decryptData(String text, String pri_key) {
try {
byte[] data = text.getBytes();
PrivateKey privateKey = getPrivateKey(Base64.decode(pri_key.getBytes(),Base64.DEFAULT));
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(data);
} catch (Exception e) {
//"java.lang.ArrayIndexOutOfBoundsException: too much data for RSA block" exception occurs here.
return null;
}
}
/**
*
* #param keyBytes
* #return
* #throws NoSuchAlgorithmException
* #throws InvalidKeySpecException
*/
public static PublicKey getPublicKey(byte[] keyBytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
return keyFactory.generatePublic(keySpec);
}
/**
*
* #param keyBytes
* #return
* #throws NoSuchAlgorithmException
* #throws InvalidKeySpecException
*/
public static PrivateKey getPrivateKey(byte[] keyBytes) throws NoSuchAlgorithmException,
InvalidKeySpecException {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
return keyFactory.generatePrivate(keySpec);
}
}
And the snippet that encrypts and decrypts data:
//encrypt
byte[] e = RSA.encryptData(text, PUBLIC_KEY);
String result = Base64.encodeToString(e, Base64.DEFAULT);
tv_encrypted.setText(result);
//decrypt
byte[] d = RSA.decryptData(text, PRIVATE_KEY);
String result = Base64.encodeToString(d, Base64.DEFAULT);
tv_decrypted.setText("Decrypted result:\n" + result);
I know the reason may be that the text to be decrypted is too long , but I just encrypt "abc" and then decrypt the encrypted "abc". And how to handle encrypting long text if the text to be encrypted or decrypted should be 11 bytes less than the rsa private key? How can I do to solve it? I'm new to RSA.
Thanks in advance!
You are missing some steps in your code which makes it impossible to check. However, there are a few clues to suggest a problem. Your decryptData method takes a String argument and then calls String.getBytes() to get the data which is then decrypted. However, the result of encryption is a sequence of bytes which is not the encoding of any valid String. Perhaps you meant to base64 decode the input instead of calling getBytes(). In general to perform decryption and decoding you must reverse the steps you performed during encryption and encoding. So, if the plaintext is a byte[] then the steps are:
byte [] → Encrypt → byte [] → Base64 encode → String.
then, in the decrypt direction you start with a Base64 string, you must, in order:
String → Base64 decode → byte [] → decrypt → byte []
Also, another issue which is bad practice and a source of many portability bugs is the use of defaults. You are using defaults in two places and they're both troublesome. First you are using the default no-args String.getBytes() method, and presumably matching that up with the one-arg String (byte []) constructor. This use the platform default character set, but this can differ on different platforms. Therefore always specify a character set. For most applications 'UTF-8' is an ideal choice. Secondly, you are calling Cipher.getInstance('RSA') without specifying padding. Oracle's Java and Android's Java will give you different padding and thus your code will not be portable between the platforms. Always specify the complete padding string. Here the choice is little more difficult if you need portability to older Java implementations. OAEP padding should be your first choice, so Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); is probably the right choice. See this for further discussion.
As for how to encrypt longer texts, see the answer from Henry.
Fianlly I modified my codes like that and they work well:
public static String encryptData(String text, String pub_key) {
try {
byte[] data = text.getBytes("utf-8");
PublicKey publicKey = getPublicKey(Base64.decode(pub_key.getBytes("utf-8"), Base64.DEFAULT));
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return Base64.encodeToString(cipher.doFinal(data),Base64.DEFAULT);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static String decryptData(String text, String pri_key) {
try {
byte[] data =Base64.decode(text,Base64.DEFAULT);
PrivateKey privateKey = getPrivateKey(Base64.decode(pri_key.getBytes("utf-8"),Base64.DEFAULT));
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return new String(cipher.doFinal(data),"utf-8");
} catch (Exception e) {
return null;
}
}
If something seems wrong still you can remind me. Thanks for James and Henry's answer.
Usually, you generate a random secret key for a symmetric cipher (like AES) and use this to encrypt your pay load.
RSA is then only used to encrypt this random key. This does not only solve the length problem but has some other advantages as well:
Symmetric cyphers are usually much faster
If the message is sent to several recievers, only the encrypted key has to be added specifically for each receiver, the main content can be the same.
For an Android app I want to obfuscate/encrypt the server public key when building with gradle.
Right now I'm obfuscating using Base64 but I need AES as an extra
task encryptKeys {
doFirst {
//Encrypt the server key
// Load key
byte[] key = new File('project/keys/server.crt.der').bytes
// Encode key twice
String encoded = key.encodeBase64().toString();
encoded = encoded.bytes.encodeBase64().toString();
//TODO AES ENCRYPTION HERE
// Save key
new File('project/src/main/assets/server.crt.der').bytes = encoded.getBytes()
Later at runtime when using this key i would decrypt it like this
public static String decrypt(byte[] cipherText) throws Exception{
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "BC");
SecretKeySpec key = new SecretKeySpec(encryptionKey.getBytes("UTF-8"), "AES");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(IV.getBytes("UTF-8")));
return new String(cipher.doFinal(cipherText),"UTF-8");
}
What would be the correct way to encrypt my key with AES in gradle script? Google couldn't help me out. Is this something that's possible at all or would I need to find another solution?
There's a similar SO question here for encrypting a string with AES in java.
I've adopted this into a gradle script below.
It will encrypt the SERVERKEY string (in your version load this from external source) with the key KEY. I don't have BouncyCastle installed, so I used SunJCE, but I left it as a parameter so you can change it easily.
The output in this simple case is the file "obf.enc". The decIt task will also decrypt and print out to show it's worked symmetrically.
Your hardest part is obviously the fact your KEY for encrypting is embedded in your application (hence my question in the comments), so this is just security through obscurity, but if that's good enough for the application, so be it.
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
import javax.crypto.Cipher
ext {
KEY = "mysecretkey".padRight(16).getBytes("UTF-8")
SERVERKEY = "serverkey"
IV = "1234".padRight(16).getBytes("UTF-8")
PROVIDER = "SunJCE"
}
task encIt << {
SecretKeySpec key = new SecretKeySpec(KEY, "AES")
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", PROVIDER)
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(IV))
def encBytes = cipher.doFinal(SERVERKEY.bytes)
def out = file('obf.enc')
out.delete()
out << encBytes
}
task decIt << {
def cipherText = file('obf.enc').bytes
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", PROVIDER)
SecretKeySpec key = new SecretKeySpec(KEY, "AES")
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(IV))
println new String(cipher.doFinal(cipherText), "UTF-8")
}