I am looking for a solution that meets the following requirements:
Let's assume, there are: the Application installed on a computing device and controlling it, Users that use this application, and Maintainers, that provide some support for the application. Application has the Configuration, for example in the file or database. Configuration is updated manually by Maintainers when required, for example weekly. Configuration contains, for example, list of emails, Application sends it's alerts to. Let's assume, that it is not possible for Users to modify the Application in any way. Although, Application is written in Java, so it is easy for Users to copy and debug it. Internally, the Application decrypts the Configuration in the Application's memory, in order to use the Configuration.
Users shall able to view the Configuration from inside the Application. Users shall be unable to change the Configuration, or to use their own (which is basically the same), for example to change any email or remove existing email or add a new one.
Additional requirement, that is not mandatory: It shall not be possible to directly view the Configuration without the Application. I understand it's hardly really possible, so, it shall be at least just difficult, like decryption necessary to view the Configuration without the Application.
Question: how to achieve this and is it possible at all?
Possible solutions I can realize, and attacks:
1) To use some signing. To sign each Configuration with some Digest and to check the Digest in the Application then. Attack: as I understand, App shall calculate the Digest using the public key stored in it. Then the Application shall compare calculated Digest with the one provided with the Configuration. So, attack is simple : Users will modify the Configuration, then debug the Application, put a breakpoint on the place where Application has already calculated Digest for comparing it with the stored one, then Users could dump the calculated Digest and replace provided Digest with this calculated one.
2) To use hybrid encryption. In this case the attack is the same: breakpoint in the place where decrypted symmetric key is available, dump this key, then to use it for the new Configuration encryption.
3) To use asymmetric encryption. Maintainers encrypt Configuration with the public key, then Application decrypts the Configuration with the private key. Attack is simple : Users could dump private key from the Application and derive a new public key, then use it for encryption.
Is there a solution, like "encrypt with the public key, then decrypt with private" for large chunks of data (up to 10 kb), or maybe any other possible way to achieve that?
Thank you
Colleagues,
I have an application that shall receive and store some read-only data. The data shall be available for users to read but not available to change. For example, assume that there are text files with data, that keep some texts available for end users to read but not available for edits. New files shall be periodically received by the application, and shall be available for users in the same read-only mode.
So, local content (for example files content) is accessible by users. Users can copy content from application, save decrypted copies and so on. The only thing I need to prevent, is to replace existing data with any other data, including changing the content or adding new by users. I do not need changes detection, I need to make changes impossible (ok, as hard as possible).
I suppose, that the easiest way to do that, is to encrypt data with some secret key and to include the public key in the app, so app could decrypt and show the data, but without the secret key users would not be able to change the content.
I know, that standard RSA supports just a small data blocks to encrypt, usually slightly less than the key length. (I made tests and found that for RSA 2048 Java throws an exception after 254 bytes) I also read, that it's not a good idea to split source data to chunks and to encrypt these chunks then. I read that it is advised to use symmetric key, like AES, for encryption and decryption, then to encrypt this AES key with RSA key pair.
I see a big (as I suppose) security risk in this scenario - as my app is written in Java, it's quite easy to debug it and to dump the decrypted AES key, then to use it for data modifications, even without any modifications of the application itself.
So, my question is: how to solve this problem and what is considered to be secure to use in such a case?
Thank you
Update:
Of course, users are able to copy the file and to use a copy as they want. The goal is, to disallow users to change data used by application, not a copy of these data. In case of asymmetric encryption it is easy to achieve - I encrypt data with my private key, pass to app, app in runtime decrypts data with it's public key and use. In case someone would like to change data, app would not decrypt the data properly and data will be spoiled and app would fail to work till data will be reverted back.
As #President James K. Polk stated in one of his comments the only solution in my humble opinion is to sign the read-only data and use it only if the data is verified. In your "Possible solution & attack" section you write that the program compares some Digits that can easily been overwritten. Usually the signature is done with the (SHA256-)
hash of the data, but you can sign the complete data without hashing it first and 4 KB of data does not bring performance issues on my desktop Java.
I setup a full working example that simulates the maintainer-side and the app-side and as a little goodie I encrypt the plaintext with AES CBC (key generated out of signature). I know that this mode of encryption is not the "best way" as the data does not need to be kept totally secret but not direct visible it's a good solution.
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.util.Arrays;
import java.util.Random;
public class Cyptosystem {
public static void main(String[] args) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, NoSuchPaddingException, InvalidAlgorithmParameterException, BadPaddingException, IllegalBlockSizeException {
System.out.println("Cryptosystem for \nhttps://stackoverflow.com/questions/62361876/asymmetric-cryptographic-algorithm-for-large-text-data/62398723#62398723");
System.out.println("Warning: this program is experimental and has no proper exception handling");
byte[] plaintext = new byte[4000]; // content to get secured, provided by maintainers
byte[] ciphertext = new byte[0]; // encryped plaintext
byte[] dataForApp = new byte[0]; // initvector | ciphertext
new Random().nextBytes(plaintext);
// generate rsa keypair
System.out.println("generate the RSA keypair");
KeyPairGenerator rsaGenerator = KeyPairGenerator.getInstance("RSA");
SecureRandom random = new SecureRandom();
rsaGenerator.initialize(4096, random);
KeyPair rsaKeyPair = rsaGenerator.generateKeyPair();
PrivateKey rsaPrivateKey = rsaKeyPair.getPrivate(); // for signature
PublicKey rsaPublicKey = rsaKeyPair.getPublic(); // for verification, implemented in app resources
System.out.println("sign & encrypt the plaintext");
// signature done by maintainers
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(rsaPrivateKey);
sig.update(plaintext);
byte[] signature = sig.sign(); // provide to app as byte array, hexstring or base64 as you like
// encrypt plaintext with signature
byte[] initvector = new byte[16];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(initvector); // random initvector
// you can use another aes mode for encryption e.g. gcm
// you can use a hmac as key derivation ...
// i'm using sha256 to get a 32 byte long key
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] aeskey = md.digest(signature);
SecretKeySpec keySpec = new SecretKeySpec(aeskey, "AES");
IvParameterSpec ivKeySpec = new IvParameterSpec(initvector);
Cipher aesCipherEnc = Cipher.getInstance("AES/CBC/PKCS5PADDING");
aesCipherEnc.init(Cipher.ENCRYPT_MODE, keySpec, ivKeySpec);
ciphertext = aesCipherEnc.doFinal(plaintext);
// copy iv | ciphertext
dataForApp = new byte[ciphertext.length + 16]; // initvector length 16 byte
System.arraycopy(initvector, 0, dataForApp, 0, initvector.length);
System.arraycopy(ciphertext, 0, dataForApp, initvector.length, ciphertext.length);
// send the dataForApp to the app (as byte array, hex string, base64 as you like
System.out.println("dataForApp length: " + dataForApp.length);
// app side, receive dataForApp & signature, already has public key
byte[] dataForAppApp = dataForApp.clone();
byte[] signatureApp = signature.clone();
System.out.println("decrypt and verify the signature");
// get initvector & ciphertext
byte[] initvectorApp = new byte[16];
byte[] ciphertextApp = new byte[(dataForAppApp.length - 16)];
System.arraycopy(dataForAppApp, 0, initvectorApp, 0, 16);
System.arraycopy(dataForAppApp,16, ciphertextApp, 0, (dataForAppApp.length - 16));
// decrypt data
MessageDigest mdApp = MessageDigest.getInstance("SHA-256");
byte[] aeskeyApp = md.digest(signature);
SecretKeySpec keySpecApp = new SecretKeySpec(aeskeyApp, "AES");
IvParameterSpec ivKeySpecApp = new IvParameterSpec(initvectorApp);
Cipher aesCipherDec = Cipher.getInstance("AES/CBC/PKCS5PADDING");
aesCipherDec.init(Cipher.DECRYPT_MODE, keySpecApp, ivKeySpecApp);
byte[] decrypttext = aesCipherDec.doFinal(ciphertextApp);
System.out.println("plaintext equals decrypttext: " + Arrays.equals(decrypttext, plaintext));
// don't use the ciphertext as the signature is not verified
Signature sigApp = Signature.getInstance("SHA256withRSA");
sigApp.initVerify(rsaPublicKey);
sigApp.update(decrypttext);
boolean signatureVerified = sigApp.verify(signatureApp);
System.out.println("signatureApp verified: " + signatureVerified);
System.out.println("if verified == true we can use the decrypttext");
}
}
Firstly, don't roll your own crypto. Cryptography is very hard, and if you make any mistake, it will have vulnerabilities you could have avoided by using a well-established library to do the heavy lifting. You could, for example, use libsodium. It has many abstractions, and probably has a solution for what you need.
With that out of the way, let's discuss how that would make it safer: the user needs to be able to read the contents, but not edit it. What exactly do you mean by "cannot edit"? Can he not be able to modify anything locally, or just not be able to upload it to your server as if he was authorized to do so?
If the former, encryption can't help you much - you need to be able to decrypt it locally, so an attacker can always dump your process' memory to get to the data - sure it would be hard, but definitely possible. Just not allowing people to edit/save/download in your application would be the strongest guarantee you can get.
If the latter, then using authentication would be the way to go - be that a simple method like HTTP basic authentication with user and password, or signing the file to be uploaded. Dealing with authentication on your application's side would be the more practical way.
Asymmetric encryption is done with the Public Key and the decryption is performed with the Private Key. As the app has to be capable to decrypt the data the app needs to know the Private Key and that's the problem with the common used algorithms, because for RSA or ECIES the Public key can get derived from the Private key. Therefore it's not a real problem to derive the Public key and store changed/appended data after encryption with the Public key.
Second thing is - you did not specify how "large" your text will be - some KB, MB, GB?
Some months ago I tested some "new" algorithms that are "Post quantum safe" and as an example I used the McEliece Fujisaki algorithm that is available with the Bouncy Castle Crypto provider (I used version 1.65, bcprov-jdk15to18-165.jar).
The program creates a 50 MB large byte array that gets encrypted with the Public key and decrypted with the Private key.
At the moment I did not find any Public key deriving methods so you definitely need to know the Private and the Public key.
I did not test larger byte arrays because this parameter depends on the memory of the target system (you need the double memory
as the complete data is captured in ciphertextByte and then again in decryptedtextByte).
Edit June 16th 2020: President James K. Polk programmed a method that easily retrieves a public key from a given private key. The source is available in his GitHub-Repo (https://github.com/james-k-polk/McEliece/blob/master/McElieceRecoverPublicFromPrivate.java) and for later convenience shown at the end of this answer. So everyone that has access to a private McEliece key is been able to encrypt data with the retrieved public key! Thanks to President James for his help.
Here are the outputs on the console:
McEliece Fujisaki Pqc Encryption
key generation
PrivateKey length: 4268 algorithm: McEliece-CCA2 format: PKCS#8
PublicKey length: 103429 algorithm: McEliece-CCA2 format: X.509
initialize cipher for encryption
pt length: 52428800 (50 mb)
ct length: 52429056 (50 mb)
initialize cipher for decryption
dt length: 52428800 (50 mb)
compare plaintext <-> decryptedtext: true
class McElieceFujisakiPqcEncryptionLargeData.java
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.pqc.crypto.mceliece.*;
import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
import org.bouncycastle.pqc.jcajce.provider.mceliece.BCMcElieceCCA2PrivateKey;
import org.bouncycastle.pqc.jcajce.provider.mceliece.BCMcElieceCCA2PublicKey;
import java.security.*;
import java.util.Arrays;
import java.util.Random;
public class McElieceFujisakiPqcEncryptionLargeData {
public static void main(String[] args) throws InvalidCipherTextException {
System.out.println("McEliece Fujisaki Pqc Encryption");
if (Security.getProvider("BCPQC") == null) {
Security.addProvider(new BouncyCastlePQCProvider());
// used Bouncy Castle: bcprov-jdk15to18-165.jar
}
System.out.println("key generation");
SecureRandom keyRandom = new SecureRandom();
McElieceCCA2Parameters params = new McElieceCCA2Parameters();
McElieceCCA2KeyPairGenerator mcElieceCCA2KeyGen = new McElieceCCA2KeyPairGenerator();
McElieceCCA2KeyGenerationParameters genParam = new McElieceCCA2KeyGenerationParameters(keyRandom, params);
mcElieceCCA2KeyGen.init(genParam);
AsymmetricCipherKeyPair pair = mcElieceCCA2KeyGen.generateKeyPair();
AsymmetricKeyParameter mcEliecePrivateKey = pair.getPrivate();
AsymmetricKeyParameter mcEliecePublicKey = pair.getPublic();
PrivateKey privateKey = new BCMcElieceCCA2PrivateKey((McElieceCCA2PrivateKeyParameters) pair.getPrivate()); // conversion neccessary only for key data
PublicKey publicKey = new BCMcElieceCCA2PublicKey((McElieceCCA2PublicKeyParameters) pair.getPublic()); // conversion neccessary only for key data
System.out.println("PrivateKey length: " + privateKey.getEncoded().length + " algorithm: " + privateKey.getAlgorithm() + " format: " + privateKey.getFormat());
System.out.println("PublicKey length: " + publicKey.getEncoded().length + " algorithm: " + publicKey.getAlgorithm() + " format: " + publicKey.getFormat());
// generate cipher for encryption
System.out.println("\ninitialize cipher for encryption");
ParametersWithRandom param = new ParametersWithRandom(mcEliecePublicKey, keyRandom);
McElieceFujisakiCipher mcElieceFujisakiDigestCipher = new McElieceFujisakiCipher();
mcElieceFujisakiDigestCipher.init(true, param);
// random plaintext
byte[] plaintext = new byte[52428800]; // 50 mb, 50 * 1024 * 1024
new Random().nextBytes(plaintext);
System.out.println("pt length: " + plaintext.length + " (" + (plaintext.length / (1024 * 1024)) + " mb)");
byte[] ciphertext = mcElieceFujisakiDigestCipher.messageEncrypt(plaintext);
System.out.println("ct length: " + ciphertext.length + " (" + (ciphertext.length / (1024 * 1024)) + " mb)");
System.out.println("\ninitialize cipher for decryption");
mcElieceFujisakiDigestCipher.init(false, mcEliecePrivateKey);
byte[] decryptedtext = mcElieceFujisakiDigestCipher.messageDecrypt(ciphertext);
System.out.println("dt length: " + decryptedtext.length + " (" + (decryptedtext.length / (1024 * 1024)) + " mb)");
System.out.println("\ncompare plaintext<-> decryptedtext: " + Arrays.equals(plaintext, decryptedtext));
}
}
Public key Retrieval class by President James K. Polk, available under MIT-Licence:
package com.github.jameskpolk;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.pqc.crypto.mceliece.*;
import org.bouncycastle.pqc.jcajce.provider.mceliece.BCMcElieceCCA2PrivateKey;
import org.bouncycastle.pqc.jcajce.provider.mceliece.BCMcElieceCCA2PublicKey;
import org.bouncycastle.pqc.math.linearalgebra.*;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
public class McElieceRecoverPublicFromPrivate {
private static final SecureRandom RAND = new SecureRandom();
public static AsymmetricCipherKeyPair generateKeyPair() {
McElieceCCA2KeyPairGenerator kpg = new McElieceCCA2KeyPairGenerator();
McElieceCCA2Parameters params = new McElieceCCA2Parameters();
McElieceCCA2KeyGenerationParameters genParam = new McElieceCCA2KeyGenerationParameters(RAND, params);
kpg.init(genParam);
return kpg.generateKeyPair();
}
public static McElieceCCA2PublicKeyParameters recoverPubFromPriv(McElieceCCA2PrivateKeyParameters priv) {
GF2mField field = priv.getField();
PolynomialGF2mSmallM gp = priv.getGoppaPoly();
GF2Matrix h = GoppaCode.createCanonicalCheckMatrix(field, gp);
Permutation p = priv.getP();
GF2Matrix hp = (GF2Matrix) h.rightMultiply(p);
GF2Matrix sInv = hp.getLeftSubMatrix();
GF2Matrix s = (GF2Matrix) sInv.computeInverse();
GF2Matrix shp = (GF2Matrix)s.rightMultiply(hp);
GF2Matrix m = shp.getRightSubMatrix();
GoppaCode.MaMaPe mmp = new GoppaCode.MaMaPe(sInv, m, p);
GF2Matrix shortH = mmp.getSecondMatrix();
GF2Matrix shortG = (GF2Matrix) shortH.computeTranspose();
// generate public key
return new McElieceCCA2PublicKeyParameters(
priv.getN(), gp.getDegree(), shortG,
priv.getDigest());
}
public static void main(String[] args) throws Exception{
// generate a McEliece key pair
AsymmetricCipherKeyPair bcKeyPair = generateKeyPair();
McElieceCCA2PrivateKeyParameters bcPriv = (McElieceCCA2PrivateKeyParameters) bcKeyPair.getPrivate();
BCMcElieceCCA2PrivateKey priv = new BCMcElieceCCA2PrivateKey(bcPriv);
// get the first public key
McElieceCCA2PublicKeyParameters bcPub1 = (McElieceCCA2PublicKeyParameters) bcKeyPair.getPublic();
BCMcElieceCCA2PublicKey pub1 = new BCMcElieceCCA2PublicKey(bcPub1);
// Now generate a second public key for the private key
McElieceCCA2PublicKeyParameters bcPub2 = recoverPubFromPriv(bcPriv);
BCMcElieceCCA2PublicKey pub2 = new BCMcElieceCCA2PublicKey(bcPub2);
// print some info about sizes
System.out.printf("Size of encrypted messages in bits(bytes): %d(%d)\n",
priv.getEncoded().length, priv.getEncoded().length / 8);
System.out.printf("private key length: %d\n", bcPriv.getK());
System.out.printf("public key1 length: %d\n", pub1.getEncoded().length);
System.out.printf("public key2 length: %d\n", pub2.getEncoded().length);
// now encrypt different messages with each public key.
String message1 = "Deposits should be made to account # 3.1415929";
String message2 = "Deposits should be made to account # 2.71828";
ParametersWithRandom params1 = new ParametersWithRandom(bcPub1, RAND);
ParametersWithRandom params2 = new ParametersWithRandom(bcPub2, RAND);
McElieceFujisakiCipher mcElieceFujisakiDigestCipher1 = new McElieceFujisakiCipher();
McElieceFujisakiCipher mcElieceFujisakiDigestCipher2 = new McElieceFujisakiCipher();
mcElieceFujisakiDigestCipher1.init(true, params1);
mcElieceFujisakiDigestCipher2.init(true, params2);
byte[] ciphertext1 = mcElieceFujisakiDigestCipher1.messageEncrypt(message1.getBytes(StandardCharsets.UTF_8));
byte[] ciphertext2 = mcElieceFujisakiDigestCipher2.messageEncrypt(message2.getBytes(StandardCharsets.UTF_8));
System.out.println("ct1 length: " + ciphertext1.length + " (" + (ciphertext1.length / (1024 * 1024)) + " mb)");
System.out.println("ct2 length: " + ciphertext2.length + " (" + (ciphertext2.length / (1024 * 1024)) + " mb)");
mcElieceFujisakiDigestCipher1.init(false, bcPriv);
mcElieceFujisakiDigestCipher2.init(false, bcPriv);
byte[] decryptedtext1 = mcElieceFujisakiDigestCipher1.messageDecrypt(ciphertext1);
byte[] decryptedtext2 = mcElieceFujisakiDigestCipher2.messageDecrypt(ciphertext2);
System.out.printf("Decrypted message 1: %s\n", new String(decryptedtext1, StandardCharsets.UTF_8));
System.out.printf("Decrypted message 2: %s\n", new String(decryptedtext2, StandardCharsets.UTF_8));
}
}
I know, that standard RSA supports just a small data blocks to encrypt,
That's why we use a hybrid cryptosystem. Data are encrypted using a symmetric cipher (data key), and the symmetric data key is encrypted using an asymmetric cipher.
I do not need changes detection, I need to make changes impossible (ok, as hard as possible).
If you are unable to enforce any read-only input/filesystem, then detecting changes is the best you can do. Either it's failed decryption or signature.
Actually to ensure data integrity I'd really use signing, not pure encryption. I see you don't want that, but at the end it will there. Some ciphers / cipher modes are malleable - data can be changed even when encrypted and without any authentication (mac, signature) the decryption is valid and you won't be able to detect the integrity failure.
If you would just rely on application to detect that data are corrupted after failed decryption, you are creating a perfect decryption oracle (breaking security)
I suppose, that the easiest way to do that, is to encrypt data with some secret key and to include the public key in the app, so app could decrypt and show the data, but without the secret key users would not be able to change the content.
Anything hardcoded in your app you can consider as revealed/public. You correctly identified the risk. If you have a dedicated user, nothing prevents the user to change the key in the app and pass invalid data. So - for anything that runs at the client, you can make the integrity stronger, but not perfect. At the end - you have to make some assumptions about adversary's abilities.
I encrypt data with my private key, pass to app, app in runtime decrypts data with it's public key and
In theory (mathematically) you can do that, but most of the current libraries will not let you use the key pairs wrong way (private key is intended for decryption or signing, public for encryption or validation). If you want to code such a solution yourself, you are in risk of creating weaknesses you may not be aware of (proper padding, timing,..)
I believe there are even some weaknesses in the scheme (encrypting using the private key), but I cannot recall details, there are people with deeper knowledge in the topic (e. g. James Polk from comments)
Edit:
Examples to create a signature or MAC : https://docs.oracle.com/javase/7/docs/technotes/guides/security/crypto/CryptoSpec.html
btw - using aes-gcm the Java Cipher implementation automatically appends the mac tag to the end of the ciphertext
An interesting question. As far as I can see from your question, the data itself is not secret. Your problem is that the user should not be able to change the data (or that you should be able to detect that he or she has changed it). In this case, a hash function (including possibly a cryptographic hash function) might well be a better approach. See https://en.wikipedia.org/wiki/Hash_function and https://en.wikipedia.org/wiki/Cryptographic_hash_function. If you use a hash function, then you can always detect whether the user has tried to change the data.
The cryptographic hash functions are one-way, so that you do not need to store any key in your program.
Related
How do I decrypt my iOS CryptoKit encrypted value on the web service side?
Similar to this SO question:
CryptoKit in Java
Or this SO question
Can I create my own SymmetricKey that we both know the string of? How can my value be decrypted in Java PhP or .NET? (I understand all these languages and can translate, the app is currently in php)
Apple's code from their playground:
let key = SymmetricKey(size: .bits256) //<--- how to share with web service???
let themeSongPath = Bundle.main.path(forResource: "ThemeSong", ofType: "aif")!
let themeSong = FileManager.default.contents(atPath: themeSongPath)!
// below code is from Apple Playground
let encryptedContentAES = try! AES.GCM.seal(themeSong, using: key).combined
/*:
The client decrypts using the same key, assumed to have been obtained out-of-band.
*/
let sealedBoxAES = try! AES.GCM.SealedBox(combined: encryptedContentAES!)
//HOW DO I DO THIS ON WEB SERVICE SIDE??? either in java or php or .net
let decryptedThemeSongAES = try! AES.GCM.open(sealedBoxAES, using: key)
assert(decryptedThemeSongAES == themeSong)
/*:
You use a sealed box to hold the three outputs of the encryption operation: a nonce, the ciphertext, and a tag.
*/
// The nonce should be unique per encryption operation.
// Some protocols require specific values to be used, such as monotonically increasing counters.
// If none is passed during the during the encryption, CryptoKit randomly generates a safe value for you.
let nonceAES = sealedBoxAES.nonce
// The ciphertext is the encrypted plaintext, and is the same size as the original data.
let ciphertextAES = sealedBoxAES.ciphertext
// The tag provides authentication.
let tagAES = sealedBoxAES.tag
// The combined property holds the collected nonce, ciphertext and tag.
assert(sealedBoxAES.combined == nonceAES + ciphertextAES + tagAES)
Link to Playground
So I guess my real questions was how do I encrypt with cryptokit and decrypt with php (web app.
These 2 links helped me:
Swift CryptoKit and Browser
iOS CryptoKit in Java
SwiftCode:
func encryptAES_GCMCryptoKit()->String {
let newkeyString1 = "I9GiP/cK4YKko8CeNF5F8X6/E6jt0QnV" //has to be 32 bytes for a 256 bit encryption or you will get the error key wrong size
let newKey = SymmetricKey(data: newkeyString1.data(using: .utf8)!)
let mySealedBox = try AES.GCM.seal(userString, using: newKey, nonce: iv)
let iv = AES.GCM.Nonce()
do{
let mySealedBox = try AES.GCM.seal(userString, using: newKey, nonce: iv)
let dataToShare = mySealedBox.combined?.base64EncodedData()
// The combined property holds the collected nonce, ciphertext and tag.
assert(mySealedBox.combined == nonceAES + ciphertextAES + tagAES)
}catch {
print("error \(error)")
}
}
Php code:
function decryptStringAES_GCM($combinedInput='') //64 base encoded combine string
{
$key = "I9GiP/cK4YKko8CeNF5F8X6/E6jt0QnV"; // <- 256 bit key - same key is on the swift side
$combined = base64_decode($combinedInput); //<- $combinedInput will be different every time even for the same value
$tag = substr($combined, -16);
$nonce = substr($combined, 0, 12);
$length = strlen($combined)-16-12; //take out tag and nonce (iv) lengths
$cipherText = substr($combined, 12, $length);
$res_non = openssl_decrypt($cipherText, 'aes-256-gcm', $key, OPENSSL_RAW_DATA| OPENSSL_NO_PADDING, $nonce, $tag);
return $res_non //decrypted string
You can also pass the key back to the server in a separate call like the first link does.
After watching the WWDC video: WWDC Cryptokit 2019 video
At around 29 min 20 seconds they advise you to get the key data from the server initially. So you can just create the key by doing this:
This way the server and the app have the same key. Or if you have control of both sides, you can know what your server key is and create the key with data from a string that you both know.
I have a String representing a symmetric key, obtained by using Hashicorp Vault (this may not be important actually). I need this key to encrypt big files, so I cannot send the file directly to Vault asking it to encrypt the data. I want to do it locally instead, so I asked Vault to create a symmetric key for me (by using the transit/datakey/plaintext/ endpoint). I have now a symmetric key (and its ciphertext) that is 44 byte long, generated with aes256_gcm96 algorithm. So my 32 byte key is wrapped with a 96 bits (12 bytes) gcm block, as far I've understood.
Now I want to use this key to encrypt my data, but the key is too long to do that, so I need somehow either to unwrap it or call some function that takes in input such a key. I was trying to use Cipher to encrypt my data. This is what I (wrongly) did so far
byte[] datakeyByteArray = mySymmetricKey.getBytes();
SecretKey secretKey = new SecretKeySpec(datakeyByteArray, "AES_256");
Cipher cipher = Cipher.getInstance("AES_256/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);`
When calling the init function, obviously, an exception is thrown: java.security.InvalidKeyException: The key must be 32 bytes
What kind of operation can I do to obtain a valid key?
Thank you.
You already get a link from #Saptarshi Basu that shows in general how to encrypt data with AES GCM. As you see with my code there is nothing really "mystic" doing this but there are some traps to run in.
Let's start with the most important information - what is the encryption key ? From Hashicorp you received an 44 bytes long string that is the pure 32 bytes long AES GCM key but in Base64-encoding. To get the key usable with Java encryption you need to decode the key to a byte array like this:
String keyBase64 = "VxJWkOYm2F5z1nF1th9zreS6ZAZMFkCq0c/Ik460ayw=";
byte[] key = Base64.getDecoder().decode(keyBase64);
The second information we do need is the AES mode - you named it correctly as AES GCM mode and as you provide Java an 32 byte = 256 bit long key it's the requested AES GCM 256 algorithm/mode.
There is a third parameter necessary for AES GCM encryption and it's the nonce (or sometimes named as initialization vector). Hashicorp tells you to use a 96 bit = 12 byte long nonce. For safety reasons it is important that you use a different nonce each time you encrypt so it is good practice to use a (secure) randomly generated nonce:
byte[] nonceRandom = new byte[12];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(nonceRandom);
Now we are ready for encryption and putting all data together, do the ".doFinal" step and we receive a byte array with the ciphertext. But stop - we need to concatenate the used nonce and the ciphertext to a larger ciphertextWithNonce this way:
nonce | ciphertext
by simply copying the nonce and ciphertext to a new byte array. This "ciphertextWithNonce" is then Base64 encoded to the final ciphertextBase64 and for upload reasons written to a file.
If you paste your own key in the beginning of the program and run it you will receive a file named "hashicorp_test.enc" that is ready for upload to your fault.
This is a sample output (yours will differ as there is a random element):
Hashicorp Vault AES GCM encryption
ciphertext: /YB+kfVlIhMowLrsnndD737o2CcyWMfr4xnAADnCBSNCSvMG25aR8UzU2ta8wLwdnHfcago/25KFJ2ky95wpFtsCNE63xRs=
ciphertext written to file: hashicorp_test.enc
used key: VxJWkOYm2F5z1nF1th9zreS6ZAZMFkCq0c/Ik460ayw=
If you like to see this code running in an online compiler here is the link:
https://repl.it/#javacrypto/SoHashicorpVaultAesGcmEncryption
This is a "proof of concept" to show in general how to perform an encryption but it lacks some critical points that I'm to lazy to make your work :-).
This example encrypts a string to an encrypted file - you will need to get the original data from a file
Having a large file you may encounter an "out of memory" error as all operations with your data are done in
your heap - for a simple calculation you will need a free memory of 4.5 * original data because you take the original data
into memory, second time you have the encrypted data in memory, third time you're copying the encrypted data to
ciphertextWithNonce and in the end (number 4) you encode all data to a base64-String. For large programs you will need to
switch to a "chunk wise" encryption, done with CiphertextOutputStream
To make the Base64-writing of the complete data a little more convenient I recommend the additional usage of Apache's Base64OutputStream (available via Maven https://mvnrepository.com/artifact/commons-codec/commons-codec).
Security warning: this code has no exception handling and is for educational purpose only.
code:
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
public class Hashicorp_Aes_Gcm_encryption {
public static void main(String[] args) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, IOException {
System.out.println("Hashicorp Vault AES GCM encryption");
// https://stackoverflow.com/questions/64714527/how-can-i-encrypt-data-with-an-already-generated-aes-256-gcm-96-key-coming-from
// paste your key here:
String keyBase64 = "VxJWkOYm2F5z1nF1th9zreS6ZAZMFkCq0c/Ik460ayw=";
// filename with ciphertext for upload
String filename = "hashicorp_test.enc";
// my sample plaintext
String plaintext = "The quick brown fox jumps over the lazy dog";
// aes gcm encryption
// decode key
byte[] key = Base64.getDecoder().decode(keyBase64);
// generate random nonce
byte[] nonceRandom = new byte[12];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(nonceRandom);
// calculate specs
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(16 * 8, nonceRandom);
// initialize cipher
Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5Padding");//NOPadding
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmParameterSpec);
// encrypt
byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
// concentenate iv + ciphertext
int ciphertextWithNonceLength = nonceRandom.length + ciphertext.length;
byte[] ciphertextWithNonce = new byte[ciphertextWithNonceLength];
System.arraycopy(nonceRandom, 0, ciphertextWithNonce, 0, nonceRandom.length);
System.arraycopy(ciphertext, 0, ciphertextWithNonce, nonceRandom.length, ciphertext.length);
String ciphertextBase64 = Base64.getEncoder().encodeToString(ciphertextWithNonce);
System.out.println("ciphertext: " + ciphertextBase64);
// save encrypted data to a file
Files.write(Paths.get(filename), ciphertextBase64.getBytes(StandardCharsets.UTF_8));
System.out.println("ciphertext written to file: " + filename);
System.out.println("used key: " + keyBase64);
}
}
We use BouncyCastle PBEWITHSHA256AND256BITAES-CBC-BC to encrypt data with our java application and store the encrypted result in a MySql Database.
Example Code:
StandardPBEStringEncryptor configurationEncryptor = new StandardPBEStringEncryptor();
configurationEncryptor.setAlgorithm("PBEWITHSHA256AND256BITAES-CBC-BC");
configurationEncryptor.setProviderName("BC");
configurationEncryptor.setSaltGenerator(new RandomSaltGenerator());
configurationEncryptor.setKeyObtentionIterations(1000);
configurationEncryptor.setPassword("aTestPassword");
String input = "A Test String!";
String cypherText = configurationEncryptor.encrypt(input);
String plainText = configurationEncryptor.decrypt(cypherText);
System.out.println("Input:" + input + " cypher:" + cypherText + " plain:" + plainText);
Output:
Input:A Test String! cypher:DhCSPbCWcZ76TUD/dDeGczlHbI9dQJyB2lKAiL7dDEk= plain:A Test String!
The cypher string above is a base64 encoded string which we store in our database.
I would now like to attempt to decrypt the cypher string stored in our database using the AES utilities provided by MySql.
I am trying to understand how the BC provider concatenates the encrypted data so that I can split it up and recreate the required parameters to enable me to decrypt the data with other tools - in this case MySql's AES_DECRYPT function.
Inspecting the code I can see that the first 16bytes of the cypher text (when base 64 decoded) is the salt, I am unsure where the init vector (IV) is stored in the remainder of the cypher text data.
If we can parse out the IV, salt and encrypted value from the string, then it should be possible to use external tools to decrypt the data.
A sample Mysql AES usage is as follows:
SET block_encryption_mode = 'aes-256-cbc';
SET #key_str = SHA2('aTestPassword',256);
SET #init_vector = RANDOM_BYTES(16);
SET #crypt_str = AES_ENCRYPT('A Test String!',#key_str,#init_vector);
SELECT AES_DECRYPT(#crypt_str,#key_str,#init_vector);
Output:
A Test String!
I would like to know how to parse the BouncyCastle cypher text to obtain its component parts, and also how to use the salt to generate the correct key hash with the number of iterations specified for use by Mysql to decrypt the data.
Any help much appreciated!
This answer is not a solution in code but will help you in finding the code.
First: you are NOT using Bouncy Castle to en-/decrypt directly - of course the cipher is used as provider for the en-/decryption.
The library that does the complete en-/decryption is JASYPT and here we can find answers for your question.
Base for my research is the GitHub https://github.com/jboss-fuse/jasypt/tree/master/jasypt/src/main/java/org/jasypt/encryption/pbe and I'm starting with "StandardPBEStringEncryptor.java":
As we are trying to understand the encryption in use I found
// The StandardPBEByteEncryptor that will be internally used.
private final StandardPBEByteEncryptor byteEncryptor;
and later the encrypt-method:
...
// The StandardPBEByteEncryptor does its job.
byte[] encryptedMessage = this.byteEncryptor.encrypt(messageBytes);
...
if (this.stringOutputTypeBase64) {
encryptedMessage = this.base64.encode(encryptedMessage);
result = new String(encryptedMessage,ENCRYPTED_MESSAGE_CHARSET);
} else {
result = CommonUtils.toHexadecimal(encryptedMessage);
}
As you get a Base64-encoded string this class just returns the encryptedMessage in Base64-encding.
Let's see the base class "StandardPBEByteEncryptor.java":
Searching for ivInUse:
// Initialization Vector to be used for encryption and decryption.
private byte[] ivInUse = null;
...
// Initialize Initialization Vector
this.ivInUse = new byte[algorithmBlockSize];
That means we do have a static IV of 16 bytes length (blocklength for AES) filled with "x00".
salt:
The DefaultSaltLength is set to 8 but when using a block cipher the salt length equals to the cipher block size (for AES 16):
// The salt size for the chosen algorithm is set to be equal
// to the algorithm's block size (if it is a block algorithm).
final int algorithmBlockSize = this.encryptCipher.getBlockSize();
if (algorithmBlockSize > 0) {
this.saltSizeBytes = algorithmBlockSize;
}
The salt is generated with the saltGenerator and after encryption it is concatenated with the ciphertext in the form salt|encryptedMessage:
encrypt:
...
// Finally we build an array containing both the unencrypted salt
// and the result of the encryption. This is done only
// if the salt generator we are using specifies to do so.
if (this.saltGenerator.includePlainSaltInEncryptionResults()) {
// Insert unhashed salt before the encryption result
return CommonUtils.appendArrays(salt, encryptedMessage);
}
The number of iterations is given by initialization (1000).
Last part to solve is the algorithm for the cipher-init and when using OpenJava 11 I find:
PBEWithHmacSHA256AndAES_256
that (hopefully) works in CBC-mode.
I need to generate public/private key for RSA algorithm on IOS device and send public key to server with encrypted text. Server must read public key and decrypt user message.
I have code on swift:
func generateKeys(){
var publicKey: SecKey?
var privateKey: SecKey?
let publicKeyAttr: [NSObject: NSObject] = [kSecAttrIsPermanent:true as NSObject, kSecAttrApplicationTag:"publicTag".data(using: String.Encoding.utf8)! as NSObject]
let privateKeyAttr: [NSObject: NSObject] = [kSecAttrIsPermanent:true as NSObject, kSecAttrApplicationTag:"privateTag".data(using: String.Encoding.utf8)! as NSObject]
var keyPairAttr = [NSObject: NSObject]()
keyPairAttr[kSecAttrKeyType] = kSecAttrKeyTypeRSA
keyPairAttr[kSecAttrKeySizeInBits] = 4096 as NSObject
keyPairAttr[kSecPublicKeyAttrs] = publicKeyAttr as NSObject
keyPairAttr[kSecPrivateKeyAttrs] = privateKeyAttr as NSObject
_ = SecKeyGeneratePair(keyPairAttr as CFDictionary, &publicKey, &privateKey)
var error:Unmanaged<CFError>?
if #available(iOS 10.0, *) {
if let cfdata = SecKeyCopyExternalRepresentation(publicKey!, &error) {
let data:Data = cfdata as Data
let b64Key = data.base64EncodedString(options: .lineLength64Characters)
print("public base 64 : \n\(b64Key)")
}
if let cfdata = SecKeyCopyExternalRepresentation(privateKey!, &error) {
let data:Data = cfdata as Data
let b64Key = data.base64EncodedString(options: .lineLength64Characters)
print("private base 64 : \n\(b64Key)")
}
}
let encrypted = encryptBase64(text: "test", key: publicKey!)
let decrypted = decpryptBase64(encrpted: encrypted, key: privateKey!)
print("decrypted \(String(describing: decrypted))")
self.dismiss(animated: true, completion: nil);
}
func encryptBase64(text: String, key: SecKey) -> String {
let plainBuffer = [UInt8](text.utf8)
var cipherBufferSize : Int = Int(SecKeyGetBlockSize(key))
var cipherBuffer = [UInt8](repeating:0, count:Int(cipherBufferSize))
// Encrypto should less than key length
let status = SecKeyEncrypt(key, SecPadding.PKCS1, plainBuffer, plainBuffer.count, &cipherBuffer, &cipherBufferSize)
if (status != errSecSuccess) {
print("Failed Encryption")
}
let mudata = NSData(bytes: &cipherBuffer, length: cipherBufferSize)
return mudata.base64EncodedString()
}
func decpryptBase64(encrpted: String, key: SecKey) -> String? {
let data : NSData = NSData(base64Encoded: encrpted, options: .ignoreUnknownCharacters)!
let count = data.length / MemoryLayout<UInt8>.size
var array = [UInt8](repeating: 0, count: count)
data.getBytes(&array, length:count * MemoryLayout<UInt8>.size)
var plaintextBufferSize = Int(SecKeyGetBlockSize(key))
var plaintextBuffer = [UInt8](repeating:0, count:Int(plaintextBufferSize))
let status = SecKeyDecrypt(key, SecPadding.PKCS1, array, plaintextBufferSize, &plaintextBuffer, &plaintextBufferSize)
if (status != errSecSuccess) {
print("Failed Decrypt")
return nil
}
return NSString(bytes: &plaintextBuffer, length: plaintextBufferSize, encoding: String.Encoding.utf8.rawValue)! as String
}
This code returns public key in PKCS1. I found the library: SwCrypt
This code helps me to convert PKCS1 into PKCS8 and read public key with java
SwKeyConvert.PublicKey.pemToPKCS1DER(publicKeyPEM)
But I can't decrypt user message. Can you help me with message decryption? I wrote small unit test.
import org.junit.Test;
import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import static org.junit.Assert.assertNotNull;
public class TestExample {
String publicKeyContent = "MIMAAiMwDQYJKoZIhvcNAQEBBQADgwACDwAwggIKAoICAQC4K4zr1jTi4SSypXbrNeGd2HbYlrDRIPsPcL5a4JwGUKXwi+Rpf8Xh0D4dcRRH+Rtd5F66aqdGnhCBKtU5XsmlT+QssIggihI0iF3LEPsMlKapDrDdSbWmuitVDSSlulReMcN3hEUl8AzlNyu817snZtYESiFxm87QV6xZAcrWzvIdyiStBbngCT/v76tOZDX56IIRGoLMi3WND7538PqqYheh2+oZk05O+Bf5LZc6YteTRLLOSyIIxesoABo8tvaFyIo2ihMcnDRnGAzOMNTLXiQdj2scAMCVr3oiLpU48+Iw8ptOUBDQioW15FsYd3ugZhUX+/mFtMFsYkJyYjyG5HCqAs2/wm6eIjjy1QQwUF2hB8Z7sqyF5KrVZOv6Q7+pB83tT02ZXcDXCdsiP10G3sA4kjc/r9TuQHjCIwZa1LO4tPaO8qAzlROHIkQ4FhdaAM9U9DUq3nBywQLcEVQmXeH1OA1ve96QbMQoN+SRPh0Kq6W0U4TbzvMskQ7bePKDjiWP2fdtgSfrnOsyJaLi04n+hDsgiMfd4N9tauSMpCY6H9l7yYPc5Z+3qG2ANhteZGa7wT1OZoGLkZV0OurnA4xkzwcB7h0RVEvABB9dtl6S60FK1NELQy6sC/HCcivo9sJ+C1g2Sln+8qEdiju86X5ja5pGiRhJAxwSp2ZKgwIDAQAB";
String encryptedMessage = "g81SOC9XOD9zq5qfyhkdP/7ronNb82g3ueDtEh711L43zPSgrFksLEdIud/1fiDcV6N97RD41vb/iXtCg2/Gu6XliEhCaoG28reetG1cBndKF9UzQw9cYChp54S1wnhBkAAZQ4Of3c77DtPBCL4gcgv2ilBTm7o+NR2wXunfJ7Olbbau+7C1pa+Qv/+sz45r4gJmQ1MfGjHtw9e/U/3vjL9BfCEPn9Mo2zAZhkI81S0Ewth+csHwb3YTlE8mtHni1fvLRVXjvHk+57U3keoYPZk+93ytFL6pqkWMk+9VbLuUFHXn1mpSMiEr9GRN6XKRvEbbPp5lI9WjwRvtWfmRm5gLY76QinTrPb0KJg7oWmEoQie5o9W6MOkD+8vYV/SkkLT855SB3O57QLKCZmlSPlccE6GWfglHhAwRwrcTDY1bO/xH38gvYYPaAJMtJKtOVrqGxNkIUPwCCkdBa9JQwDSyTYxeh8AxC0ACs9cYVjMPrmC9zIZuRbmcneIGSugtzMZmI9qbLtW1aMlWuGrVyVhJlcCZuTJXWyBgx8xj8coX9YwUXSi1A4dL/Hl5Sme+HhAQs7OcH6ZZpsPmIIozXxHgOMhUo8k++cWg6+pudSoB2tr4NhxX/ID2jd1ELsg1C6mbxaKaGgXwfU9w4ZngbRxGTBlKWXwUP/xBa5BARZ4=";
#Test
public void encryptTest() throws Exception {
PublicKey publicKey = convertPublicKey(publicKeyContent);
assertNotNull(publicKey);
String s = decryptString(publicKey, encryptedMessage);
assertNotNull(s);
}
private PublicKey convertPublicKey(String publicKey) throws RSAAlgorithmException {
try {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
//generate public key
byte[] publicBytes = Base64.getDecoder().decode(publicKey);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicBytes);
return keyFactory.generatePublic(keySpec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new RSAAlgorithmException("Unable to generate public key from string " + publicKey + " . " + e.getMessage());
}
}
private String decryptString(PublicKey publicKey, String value) throws Exception {
byte[] decodedBytes;
try {
Cipher c = Cipher.getInstance("RSA/ECB/PKCS1Padding");
c.init(Cipher.DECRYPT_MODE, publicKey);
decodedBytes = c.doFinal(value.getBytes());
} catch (Exception e) {
System.out.println("Error = " + e);
throw new Exception(e);
}
return new String(decodedBytes);
}
}
I have next error:
java.lang.Exception: javax.crypto.IllegalBlockSizeException: Data must not be longer than 512 bytes
In an asymmetric cryptosystem, you have a key pair consisting of both a public and a private key.
You encrypt with the public key and you decrypt with the private key. The public key can be shared (publicly) with other parties, enabling them to send you encrypted messages. The private key is kept secret so that only you can decrypt messages encrypted with your public key.
You normally don't encrypt messages directly with RSA, since the message has to be shorter than the modulus and it might have security implications. What you do instead is, you generate a random key for a symmetric encryption scheme, for example AES-256-CTR (or AES-256-GCM if you need authentication in addition to secrecy), encrypt the message with the symmetric encryption scheme, encrypt the key for the symmetric cipher with the asymmetric encryption scheme and send both the (asymmetrically) encrypted key and the (symmetrically) encrypted message to the receiver.
The receiver will first use his/her private key to decrypt the key for the symmetric encryption scheme, then use that to decrypt the actual message. This is sometimes referred to as "hybrid encryption" and it enables the message to be (more or less) arbitrarily long.
So, what you have to do is the following.
You have to generate a key pair for the receiver of the encrypted message. Therefore, if your communication is one-way (iOS device sends data to server, but no data ever comes back), you need to generate a key pair for your server only. If your server needs to talk back, you need to generate a key pair for your client as well.
In order to send an encrypted message to the server, the client needs to have the public key of your server. Therefore, you have to somehow transfer it there. The problem is that this transfer needs to be secure, otherwise an attacker may impersonate the server, present you his/her public key instead (for which he/she knows the private counterpart), intercept all traffic, decrypt it with his/her private key, re-encrypt it with the server's public key and pass it on to the server. This is called a man in the middle attack and enables the attacker to intercept (and possibly manipulate) all communication between you and the server. Therefore, your best choice might be not to exchange public keys at all but rather to embed them into the application. This will prevent man in the middle attacks, as long as the application code can be shared by an authenticated means.
When you want to send a message to the server, generate a random symmetric encryption key (with a cryptographically secure random number generator - this is not your language's default "random" function), encrypt the message with it and an appropriate symmetric encryption scheme, which you choose according to your requirements (e. g. authentication required? then use AES-GCM - only secrecy required? then use AES-CTR). Most encryption schemes also require a random (unpredictable) initialization vector which you also generate with a CSPRNG and have to send along to the receiver since it's required for decryption, but needs not be kept secret.
Encrypt the key for the symmetric encryption scheme with an asymmetric encryption scheme and the server's public key. RSA-PKCS1 is "dated". I'd try to use RSA-OAEP instead since it has more desirable security properties. Send the encrypted key to the server.
The server decrypts the key for the symmetric encryption scheme with the asymmetric encryption scheme and his private key (which is kept secret). Then it decrypts the message with the symmetric encryption scheme.
Since most of this is complicated and a lot of subtle details can lead to security breaches, I'd suggest you do not implement this yourself. I'd suggest you just use TLS (possibly with a restricted parameter set) and implement your own certificate validator where you compare the server's public key to a known-good value to get rid of the entire PKI stuff, which costs money and also is not very secure in the first place. At least, that's how I would do it.
Alternatively, if you want to roll out your own, "proprietary" protocol, you can try to use one of the more "developer friendly" cryptographic libraries, especially NaCl. This abstracts away a lot of the "gory details" and chooses lots of sane defaults for you, which cannot be overridden, all of which makes it a lot harder to implement insecure protocols.
Keep in mind this is not to say you're "too dumb". It's just the proper way of doing these things. When it comes to crypto, the less "DIY", the better. The more widespread the crypto is, that you use, the more it gets reviewed and the quicker flaws will get fixed, so using something like NaCl, which is used in thousands of applications, is pretty neat. As long as other NaCl applications are secure, your application is (probably) secure as well. When a breach is found, NaCl will get updated, you just update the library in your application and are automatically safe, so you're left with (almost) no need for internal review and patching and your windows of vulnerability will (usually) be short.
I have some data from a external party which is encrypted according to them in: 'Rijndeal 256 with the private key'
Alongside these records there are a public and private key certificate which look like RSA certificates.
From what i've learned so far it seems the common way to use encryption with certifcates is to generate a 'secret key' or some kind in initialization vector and use this to encrypted text. So i'm thinking this is probably what they have done (the data was encrypted by a PHP application)
I'm trying to decrypt this text with javax.crypto.Cipher but i think i problably need more information on the specific encryption, but i dont really know what information to ask for, and think its likely the 'default options' will probably work. (Communication with the supplying party is difficult and slow).
i'm currently Using the following code to get the private key:
InputStreamReader ir = new InputStreamReader(the_inputstream_for_the_private_key_record);
Security.addProvider(new BouncyCastleProvider());
pemr = new PEMReader(ir);
Object o = pemr.readObject();
keyPair kp = (KeyPair) o;
return kp.getPrivate();
This seems to work as i get a instantiated PrivateKey object without errors the toString looks like:
RSA Private CRT Key
modulus: c98faa50ba69<trimmed>
public exponent: 10001
private exponent: bb889fbe5cb2a6763f...<trimmed>
primeP: eb73e85dc636f5751b...<trimmed>
primeQ: db269bd603a2b81fc9...<trimmed>
primeExponentP: 85b9f111c190595cc8...<trimmed>
primeExponentQ: a66d59a75bb77530de...<trimmed>
crtCoefficient: 79415b078c4c229746...<trimmed>
For each record i also have a entry like the following:
{
"decryptedLength":128389,
"symKeyLength":32,
"symKey":"SImE8VnSZaAu1Ve...<trimmed (this is always 685 chars long) >...ayaJcnpSeOqAGM7q="
}
Basically this is where i'm a bit stuck.
My guess would be that that 'symkey' value is encrypted with RSA which in turn when decrypted would yield the secretKey for the AES part, but if i try:
Cipher rsaCipher = Cipher.getInstance("RSA");
rsaCipher.init(Cipher.DECRYPT_MODE, key);
byte[] b = rsaCipher.doFinal('symkey'.getbytes());
this gets me "javax.crypto.IllegalBlockSizeException: Data must not be longer than 512 bytes", which seems logical since this string is 685characters long
I'm probably missing something very obvious here...
Any suggestions are appreciated.
Just guessing, but I think the value
"symKey":"SImE8VnSZaAu1Ve...<trimmed (this is always 685 chars long) >...ayaJcnpSeOqAGM7q="
is the base64 encoded output from RSA encryption using a 4096-bit public key. You need to first base64 decode the value into a byte[] array, then decrypt it with the private key, the result of which will be a 256-bit key. Note that "Rijndael 256" is ambiguous, since Rijndael supports both a 256 bit blocksize and also a 256 bit keysize.
with GregS's answer i finaly got this to work.
(adding an answer in case someone else needs to decrypt similar php encoded stuff).
The first part was to decrypt de symmetricKey ("symkey") from the metaData string
This was as Greg notes a Base64 encoded, RSA encrypted key which was decoded like so:
Cipher rsaCipher = Cipher.getInstance("RSA");
rsaCipher.init(Cipher.DECRYPT_MODE, key);
byte[] encryptedRijndaelKey = Base64.decodeBase64(base64EncodedSymetricKey); //from the metaData
byte[] rijndaelKeyBytes = rsaCipher.doFinal(encryptedRijndaelKey);
This Rijndael key was then used to decrypt de actual encrypted data like so:
RijndaelEngine rijndaelEngine = new RijndaelEngine(256); // *1 *2
KeyParameter keyParam = new KeyParameter(rijndaelKeyBytes)
rijndaelEngine.init(false, keyParam); //false == decrypt
PaddedBufferedBlockCipher bbc = new PaddedBufferedBlockCipher(rijndaelEngine, new ZeroBytePadding()); // *3
byte[] decryptedBytes = new byte[decryptedLenght]; //from the storageOptions string
int processed = bbc.processBytes(inputBytes, 0, inputBytes.length, decryptedBytes, 0);
bbc.doFinal(decryptedBytes, processed);
*1 because the Sun JCA only supports common AES which has a 128bits keysize i had to use a different provider (BouncyCastle).
*2 apparently the blocksize was also 256 bits (trail & error)
*3 apparently there was no padding used, thus the ZeroPadding for padding (again trail & error).
The symKey value is Base64 encoded and must be decoded before you can do the private key decryption on it. Also, the symmetric encryption sounds like it is AES-256. (AES is based on the Rijndael cipher).