25

I am trying to use Curve25519 in my Android app to encrypt/decrypt AES encryption key locally. I don't need any key exchange, key agreement or signing. Why I need to use that particular curve? Because I need to be able to provide private key myself and be able to calculate it's matching public key. So as far as I got, only Curve25519 does this. Please correct me if I am wrong.

All Curve25519 implementations just do key generation, key exchange and signing/verifying.

Is it possible to do data encryption/decryption after I get Curve25519 private/public keys or maybe you can suggest any alternative curves that matches my criteria?

Edit

So why I need this? I'll explain more carefully. My app is doing local file encryption, particularly photos. My goal is to be able for user to take a photo without entering a password and then enter password to view them. For this I need to be able to create public/private keypair from password and be able to recreate on the fly the same keypair whenever same password is provided. So on first run I generate ECC keypair from password and store public key on the device. When user wants to take a new photo app encrypts photo with random 256 bit AES key and then encrypt that key with stored public key. When user wants to view photo, he/she provides correct password, I derive same ECC keypair and decrypt AES key with my private key and then I can decrypt the photo using AES 256.

So as far as I get Curve25519 can give me this ability or there are any other alternatives. Code examples in Java are welcomed!

Alex Amiryan
  • 1,374
  • 1
  • 18
  • 30
  • You can probably use [this](http://stackoverflow.com/a/30014831/1816580) to instantiate ECIES. – Artjom B. Nov 07 '15 at 16:16
  • Do a key exchange with the receivers public key and use the shared key to encrypt with symmetric encryption such as XSalsa20Poly1305 or AES-GCM. – CodesInChaos Nov 07 '15 at 16:16
  • Probably you didn't get that. I don't have any receiving party, all encryption and decryption are local. – Alex Amiryan Nov 07 '15 at 16:49
  • You should also be able to compute the public key for private keys of any other curve, although could be that an API doesn't allow you to do so. ECIES only requires ephemeral-static Diffie-Hellman, so that should be possible if you have an (EC)DH implementation. – Maarten Bodewes Nov 07 '15 at 17:28
  • "Because I need to be able to provide private key myself and be able to calculate it's matching public key. " That's not a very common use case, do you have any reason why you cannot store the *public key* anywhere? Isn't it possible to use the secret to encrypt the EC private key? – Maarten Bodewes Nov 07 '15 at 17:36
  • Yes, this is exactly my use case. I need to generate EC keypair from user provided password(after password passes through many rounds of PBKDF2 of course) and recreate that same keypair as soon as password matches again. The thing is that encrypted files should be portable and I don't want to include encrypted private/public key with every file. And why I need EC at all? Because I need to be able to create new encrypted files without user entering password, but decrypt them only after user has provided valid password. I hope now it's clear what I am doing. – Alex Amiryan Nov 07 '15 at 18:17
  • So how would the user get the public key? All said and done, I don't think your scheme is viable as it is written down now. – Maarten Bodewes Nov 07 '15 at 19:07
  • So user enters password, we process password through PBKDF function and we feed the result to Curve25519 as a private key. The beauty of that curve that it can compute matching public key for that private key. We store public key on the disk. Then when we want to create new encrypted file, we generate random 256 bit key for AES, encrypt file with that key and then encrypt the key with previously stored ECC public key and store that along with the encrypted file. When user want to decrypt the file, he should provide correct password to generate same ECC keypair and decrypt AES key with private – Alex Amiryan Nov 07 '15 at 19:36
  • 2
    @AlexAmiryan You still have two parties, even if they're on the same system. The user is the receiver and the whatever encrypts the file is the sender. If you had only a single party, using asymmetric encryption would be pointless. – CodesInChaos Nov 08 '15 at 08:57
  • Your scheme is almost correct, you only need to change the part where you encrypt the AES key with Curve25519 and derive the AES key from the shared key using a KDF instead, which is ECIES. If you really want you can generate a random AES key, and then use ECIES to encrypt it with another AES key. – CodesInChaos Nov 08 '15 at 08:57
  • @CodesInChaos can you please point to some example on how to do ECIES with Curve25519 keys? Thank you – Alex Amiryan Nov 08 '15 at 09:01
  • 1
    @AlexAmiryan You can NaCl's box with a one-time sender key. That's nearly the same thing as ECIES. There are several implementations of NaCl's crypto, including NaCl itself, TweetNaCl, LibSodium or my C# port. Just create a new key, compute the shared key with the recipient, derive your AES key from the shared key (using a KDF or hash). Include the sender's public key with the ciphertext, so the recipient can compute the same shared key. – CodesInChaos Nov 08 '15 at 09:04

1 Answers1

3

They key to encrypting files on an Android device is to never store the key. To this you've added the constraint that encrypting pictures should not require a password. Finally, because asymmetric encryption is slow, you would need AES to do the heavy lifting.

This works with RSA or any other asymmetric algorithm.

Initial Run

  1. On first run, generate a keypair and require the user to input a password.
  2. Store the public key in clear. Encrypt the private key using an AES key generated from the password.

Encryption

  1. Generate a different AES key for each picture and encrypt the image with it. Encrypt the AES key with the public key and store it alongside the picture.

Decryption

  1. Have the user input the password. Generate the first AES key from it. Use it to decrypt the private key. Decrypt the AES key for this image using the private key. Use the AES key to decrypt the file and display it.

(We actually have 4 keys. We need the key pair to allow us to encrypt without a password. We need the first AES key to store the private key securely. We need the second and changing AES key to encrypt the file.)

I think this should be secure, except for attacks such as key logging. The Java code should be very straightforward. Hope this is clear.

======================================================================

Complete example Using AES and RSA. For Curve, switch the RSA code only, though it isn't supported out of the box, so you'll need an external library. Also, in the interest of time and brevity, the code is less secure than it should be. For example, I used ECB and not CBC.

package il.co.falk;

import javax.crypto.*;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;

public class SecureFile {
private PublicKey publicKey;
private byte[] privateKeyArray;
private byte[] salt = {1,2,3,4,5,6,7,8};


public static void main(String[] args) {
    String password = "PASSWORD";
    SecureFile secureFile = new SecureFile(password);
    secureFile.test();
}


public void test() {
    String password = "PASSWORD";
    String imageFile = "348756348975634897562398479623896";

    ImageAndKey imageAndKey = encryptImage(imageFile.getBytes());
    byte[] decryptedImage = decryptImage(imageAndKey, password);

    System.out.println(new String(imageFile));
    System.out.println(new String(decryptedImage));
}

public SecureFile(String password) {
    try {
        generateRSAKeys(password);
    } catch (Exception e) {
        e.printStackTrace();
    }
}



public ImageAndKey encryptImage(byte[] imageBytes) {
    try {
        byte[] secretKeyBytes = generateAESKey();
        byte[] encryptedFile = aesEncrypt(imageBytes, secretKeyBytes);
        byte[] encryptedKey = rsaEncrypt(secretKeyBytes);

        return new ImageAndKey(encryptedFile, encryptedKey);
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }

}

public byte[] decryptImage(ImageAndKey imageAndKey, String password) {
    try {
        byte[] secretKeyBytes = generateAESKey(password);
        byte[] decryptedPrivateKey = aesDecrypt(privateKeyArray, secretKeyBytes);
        byte[] decryptedKey = rsaDecrypt(imageAndKey.aesKey, decryptedPrivateKey);

        SecretKey secretKey = new SecretKeySpec(decryptedKey, "AES");
        secretKeyBytes = secretKey.getEncoded();

        byte[] decryptedBytes = aesDecrypt(imageAndKey.imageBytes, secretKeyBytes);

        return  decryptedBytes;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}



// RSA
private void generateRSAKeys(String password) throws Exception {
    final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
    keyGen.initialize(512); // TODO: make this 2048 at least
    final KeyPair keyPair = keyGen.generateKeyPair();
    publicKey = keyPair.getPublic();
    PrivateKey privateKey = keyPair.getPrivate();

    byte[] secretKeyBytes = generateAESKey(password);
    byte[] privateKeyBytes = privateKey.getEncoded();
    privateKeyArray = aesEncrypt(privateKeyBytes, secretKeyBytes);
}

public byte[] rsaEncrypt(byte[] plainText) throws Exception {
    final Cipher cipher = Cipher.getInstance("RSA");
    cipher.init(Cipher.ENCRYPT_MODE, publicKey);
    byte[] cipherText = cipher.doFinal(plainText);
    return cipherText;
}

public byte[] rsaDecrypt(byte[] cipherText, byte[] decryptedPrivateKeyArray) throws Exception {
    PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decryptedPrivateKeyArray));

    final Cipher cipher = Cipher.getInstance("RSA");
    cipher.init(Cipher.DECRYPT_MODE, privateKey);
    byte[]  plainText = cipher.doFinal(cipherText);
    return plainText;
}

// AES
private byte[] aesEncrypt(byte[] plainText, byte[] secretKeyBytes) throws Exception {
    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(secretKeyBytes));
    byte[] cipherText = cipher.doFinal(plainText);
    return cipherText;
}

public byte[] aesDecrypt(byte[] cipherText, byte[] secretKeyBytes) throws Exception {
    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
    cipher.init(Cipher.DECRYPT_MODE, getSecretKey(secretKeyBytes));
    byte[] plainText = cipher.doFinal(cipherText);
    return plainText;
}

private byte[] generateAESKey() throws Exception {
    KeyGenerator keyGen = KeyGenerator.getInstance("AES");
    keyGen.init(256);
    SecretKey secretKey = keyGen.generateKey();
    return secretKey.getEncoded();
}

private byte[] generateAESKey(String password) throws Exception {
    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
    KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 256);
    SecretKey tmp = factory.generateSecret(spec);
    SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
    return secret.getEncoded();
}

private SecretKey getSecretKey(byte[] secretKeyBytes) throws Exception {
    SecretKey secretKey = new SecretKeySpec(secretKeyBytes, "AES");
    return secretKey;
}



// Classes
class ImageAndKey {
    public byte[] imageBytes;
    public byte[] aesKey;

    public ImageAndKey(byte[] imageBytes, byte[] aesKey) {
        this.imageBytes = imageBytes;
        this.aesKey = aesKey;
    }
}

}

Roy Falk
  • 1,685
  • 3
  • 19
  • 45
  • 1
    What you described was clear beforehand and I am going to that way. What I need is the code snippet, just an example of key generation and data encryption/decryption using Curve25519 ECC. – Alex Amiryan Feb 24 '16 at 02:09
  • Any reason it has to be curve? It's not part of the standard Android and AES256 and RSA are. – Roy Falk Feb 24 '16 at 09:07