diff options
author | YuutaW <17158086+Trumeet@users.noreply.github.com> | 2019-02-28 19:50:55 -0800 |
---|---|---|
committer | YuutaW <17158086+Trumeet@users.noreply.github.com> | 2019-02-28 19:50:55 -0800 |
commit | 39de35e09424c573670d4c56742c17a3bdbe8108 (patch) | |
tree | 7b339eae41a14d0e54da967b65c2c78e66fcd9f0 /app/src/main/java/moe/yuuta/gplicense/AESObfuscator.java | |
parent | 1ff7d4d73a0c7d89487f40ccdab7433685e2200b (diff) | |
download | WorkMode-39de35e09424c573670d4c56742c17a3bdbe8108.tar WorkMode-39de35e09424c573670d4c56742c17a3bdbe8108.tar.gz WorkMode-39de35e09424c573670d4c56742c17a3bdbe8108.tar.bz2 WorkMode-39de35e09424c573670d4c56742c17a3bdbe8108.zip |
feat(app): implement Google Play App Licensing
Signed-off-by: YuutaW <17158086+Trumeet@users.noreply.github.com>
Diffstat (limited to 'app/src/main/java/moe/yuuta/gplicense/AESObfuscator.java')
-rw-r--r-- | app/src/main/java/moe/yuuta/gplicense/AESObfuscator.java | 94 |
1 files changed, 94 insertions, 0 deletions
diff --git a/app/src/main/java/moe/yuuta/gplicense/AESObfuscator.java b/app/src/main/java/moe/yuuta/gplicense/AESObfuscator.java new file mode 100644 index 0000000..7370f58 --- /dev/null +++ b/app/src/main/java/moe/yuuta/gplicense/AESObfuscator.java @@ -0,0 +1,94 @@ +package moe.yuuta.gplicense; + +import java.io.UnsupportedEncodingException; +import java.security.GeneralSecurityException; +import java.security.spec.KeySpec; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; + +import moe.yuuta.gplicense.util.Base64; +import moe.yuuta.gplicense.util.Base64DecoderException; + +/** + * An Obfuscator that uses AES to encrypt data. + */ +public class AESObfuscator implements Obfuscator { + private static final String UTF8 = "UTF-8"; + private static final String KEYGEN_ALGORITHM = "PBEWITHSHAAND256BITAES-CBC-BC"; + private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding"; + private static final byte[] IV = + {16, 74, 71, -80, 32, 101, -47, 72, 117, -14, 0, -29, 70, 65, -12, 74}; + private static final String header = "moe.yuuta.workmode.AO-1|"; + + private Cipher mEncryptor; + private Cipher mDecryptor; + + /** + * @param salt an array of random bytes to use for each (un)obfuscation + * @param applicationId application identifier, e.g. the package name + * @param deviceId device identifier. Use as many sources as possible to + * create this unique identifier. + */ + public AESObfuscator(byte[] salt, String applicationId, String deviceId) { + try { + SecretKeyFactory factory = SecretKeyFactory.getInstance(KEYGEN_ALGORITHM); + KeySpec keySpec = + new PBEKeySpec((applicationId + deviceId).toCharArray(), salt, 1024, 256); + SecretKey tmp = factory.generateSecret(keySpec); + SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES"); + mEncryptor = Cipher.getInstance(CIPHER_ALGORITHM); + mEncryptor.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(IV)); + mDecryptor = Cipher.getInstance(CIPHER_ALGORITHM); + mDecryptor.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(IV)); + } catch (GeneralSecurityException e) { + // This can't happen on a compatible Android device. + throw new RuntimeException("Invalid environment", e); + } + } + + public String obfuscate(String original, String key) { + if (original == null) { + return null; + } + try { + // Header is appended as an integrity check + return Base64.encode(mEncryptor.doFinal((header + key + original).getBytes(UTF8))); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("Invalid environment", e); + } catch (GeneralSecurityException e) { + throw new RuntimeException("Invalid environment", e); + } + } + + public String unobfuscate(String obfuscated, String key) throws ValidationException { + if (obfuscated == null) { + return null; + } + try { + String result = new String(mDecryptor.doFinal(Base64.decode(obfuscated)), UTF8); + // Check for presence of header. This serves as a final integrity check, for cases + // where the block size is correct during decryption. + int headerIndex = result.indexOf(header + key); + if (headerIndex != 0) { + throw new ValidationException("Header not found (invalid data or key)" + ":" + + obfuscated); + } + return result.substring(header.length() + key.length(), result.length()); + } catch (Base64DecoderException e) { + throw new ValidationException(e.getMessage() + ":" + obfuscated); + } catch (IllegalBlockSizeException e) { + throw new ValidationException(e.getMessage() + ":" + obfuscated); + } catch (BadPaddingException e) { + throw new ValidationException(e.getMessage() + ":" + obfuscated); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("Invalid environment", e); + } + } +}
\ No newline at end of file |