/* * Copyright 2015 Daniel Dittmar * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package dan.dit.whatsthat.util.general; import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Base64; import android.util.Log; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.SecureRandom; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.RSAKeyGenParameterSpec; import java.security.spec.X509EncodedKeySpec; import java.util.Arrays; import javax.crypto.Cipher; import dan.dit.whatsthat.util.image.ExternalStorage; /** * Non high security cryptography helper class to asymmetrically * encrypt and decrypt strings. Offers a public key to encrypt messages * that can only be read by the developers or people that can break 1024 bit codes.<br> * Offers also a way to generate a key pair and reading these keys from a file in external storage. * This is especially important as the secret key must not be included in the source code! * Created by daniel on 04.08.15. */ public class SimpleCrypto { private static final String PUBLIC_KEY_FILE = "dev_key_public.txt"; private static final String PRIVATE_KEY_FILE = "dev_key_private.txt"; private static final String DEVELOPER_PUBLIC_KEY_ENCODED = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCYT5vZ5Wof4Hh3hgNjVVAd13bUrPnqyiHXqCRT\n" + "zvEUVPAnokpr+Uw2Ft2YFPSw9J4USHrqWqVdumiABJameWx6MuvNPUU4yNd/xWd3UYpCMwJHaJm3\n" + "WP481XbUk5qU5JZWAPPZGHYBEm5FXA1kC5L8jfT41+F1ca2R0dA7S3GXEQIDAQAB"; private static Key DEVELOPER_PUBLIC_KEY; private SimpleCrypto() {} private static String encodeToString(byte[] data) { return Base64.encodeToString(data, Base64.DEFAULT); } private static byte[] encodedToBytes(String encoded) { return Base64.decode(encoded, Base64.DEFAULT); } /** * Saves the given key pair to external storage in two separate files writing * only the key encoded by Base64 with default settings. No descriptive or other metadata * is provided to and into the file. * @param pair The key pair to save to files. */ public static void saveKeyPair(@NonNull KeyPair pair) { DEVELOPER_PUBLIC_KEY = pair.getPublic(); FileWriter writer = null; try { writer = new FileWriter(new File(ExternalStorage.getExternalStoragePathIfMounted(null) + "/" + PUBLIC_KEY_FILE)); writer.write(encodeToString(pair.getPublic().getEncoded())); } catch (IOException e) { Log.e("HomeStuff", "Error during writing public key." + e); } finally { if (writer != null) { try { writer.close(); } catch (IOException ioe) { Log.e("HomeStuff", "Error closing file writer while writing public key. " + ioe); } } } try { writer = new FileWriter(new File(ExternalStorage.getExternalStoragePathIfMounted(null) + "/" + PRIVATE_KEY_FILE)); writer.write(encodeToString(pair.getPrivate().getEncoded())); } catch (IOException e) { Log.e("HomeStuff", "Error during writing private key." + e); } finally { if (writer != null) { try { writer.close(); } catch (IOException ioe) { Log.e("HomeStuff", "Error closing file writer while writing private key. " + ioe); } } } } /** * Retrieves the fixed developer public key. The key is initialized the first * time this method is invoked and future invocations will return the same key. * This method will return a static key but can be altered to read the key from the file * if needed for debugging. * @return The developer's public key. Can be null on error. */ public static synchronized Key getDeveloperPublicKey() { if (DEVELOPER_PUBLIC_KEY != null) { return DEVELOPER_PUBLIC_KEY; } String keyEncoded = DEVELOPER_PUBLIC_KEY_ENCODED; if (TextUtils.isEmpty(keyEncoded)) { // attempt to read key from file StringBuilder builder = new StringBuilder(); FileReader reader = null; try { reader = new FileReader(new File(ExternalStorage.getExternalStoragePathIfMounted(null) + "/" + PUBLIC_KEY_FILE)); char[] buffer = new char[64]; int read; while ((read = reader.read(buffer)) > 0) { builder.append(buffer, 0, read); } } catch (Exception e) { Log.e("HomeStuff", "Error trying to read public key file: " + e); return null; } finally { if (reader != null) { try { reader.close(); } catch (IOException ioe) { Log.e("HomeStuff", "Error closing file reader when reading public key file."); } } } keyEncoded = builder.toString(); } X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(SimpleCrypto.encodedToBytes(keyEncoded)); try { KeyFactory keyFactory = KeyFactory.getInstance("RSA"); DEVELOPER_PUBLIC_KEY = keyFactory.generatePublic(x509KeySpec); return DEVELOPER_PUBLIC_KEY; } catch (Exception e) { Log.e("HomeStuff", "Error with keyfactory when decoding public key" + e); return null; } } /** * Returns the developer's private key by reading it from file. * The key is not kept in memory, but this is not top secret so pieces * can and will be leaked into memory. * @return The developer's private key read from file or null on error. */ public static Key getDeveloperPrivateKey() { StringBuilder builder = new StringBuilder(); FileReader reader = null; try { reader = new FileReader(new File(ExternalStorage.getExternalStoragePathIfMounted(null) + "/" + PRIVATE_KEY_FILE)); char[] buffer = new char[64]; int read; while ((read = reader.read(buffer)) > 0) { builder.append(buffer, 0, read); } Arrays.fill(buffer, '\0'); } catch (Exception e) { Log.e("HomeStuff", "Error trying to read private key file: " + e); return null; } finally { if (reader != null) { try { reader.close(); } catch (IOException ioe) { Log.e("HomeStuff", "Error closing file reader when reading private key file."); } } } byte[] data = SimpleCrypto.encodedToBytes(builder.toString()); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(data); try { KeyFactory keyFactory = KeyFactory.getInstance("RSA"); Key privateKey = keyFactory.generatePrivate(keySpec); Arrays.fill(data, (byte) 0); return privateKey; } catch (Exception e) { Log.e("HomeStuff", "Error with keyfactory when decoding private key" + e); return null; } } /** * Generates a new 2014-bit RSA key pair. * @return A new RSA key pair or null on error. */ public static KeyPair generateKeyPair() { // Generate key pair for 1024-bit RSA encryption and decryption try { SecureRandom random = new SecureRandom(); RSAKeyGenParameterSpec spec = new RSAKeyGenParameterSpec(1024, RSAKeyGenParameterSpec.F4); KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); generator.initialize(spec, random); return generator.genKeyPair(); } catch (Exception e) { Log.e("HomeStuff", "RSA key pair error" + e); return null; } } /** * Encrypts the given data string with the given public key, returning * the encrypted encoded data in Base64. * @param publicKey The public key used for the RSA encryption. * @param data The data to encrypt. * @return The encrypted data or null on error or illegal parameter. */ public static String encrypt(Key publicKey, String data) { if (publicKey == null || TextUtils.isEmpty(data)) { return null; } // Encode the original data with RSA public key byte[] encodedBytes; try { Cipher c = Cipher.getInstance("RSA/ECB/PKCS1Padding"); c.init(Cipher.ENCRYPT_MODE, publicKey); encodedBytes = c.doFinal(data.getBytes()); return encodeToString(encodedBytes); } catch (Exception e) { Log.e("HomeStuff", "RSA encryption error"); return null; } } /** * Decrypts the given encrypted encoded data in Base64 using the given * private key. * @param privateKey The private RSA key belonging to the key pair with * the public key used for encrypting the given encrypted data. * @param encrypted The encrypted data to decrypt. * @return The decrypted data or null on error or if illegal parameter were given. */ public static String decrypt(Key privateKey, String encrypted) { if (privateKey == null || TextUtils.isEmpty(encrypted)) { return null; } // Decode the encoded data with RSA private key byte[] decodedBytes; try { Cipher c = Cipher.getInstance("RSA/ECB/PKCS1Padding"); c.init(Cipher.DECRYPT_MODE, privateKey); decodedBytes = c.doFinal(encodedToBytes(encrypted)); return new String(decodedBytes); } catch (Exception e) { Log.e("HomeStuff", "RSA decryption error"); return null; } } }