├── core ├── .gitignore ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── cpp │ │ │ ├── crypto │ │ │ │ ├── base64.h │ │ │ │ ├── crypto_wrapper.h │ │ │ │ ├── crypto_wrapper.cpp │ │ │ │ ├── base64.cpp │ │ │ │ ├── aes.h │ │ │ │ └── aes.cpp │ │ │ ├── CMakeLists.txt │ │ │ └── secure-keys.cpp │ │ └── java │ │ │ └── com │ │ │ └── u │ │ │ └── securekeys │ │ │ └── SecureEnvironment.java │ └── test │ │ └── java │ │ └── com │ │ └── u │ │ └── securekeys │ │ └── SecureEnvironmentTest.java ├── proguard-rules.pro └── build.gradle ├── annotation ├── .gitignore ├── build.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── u │ │ └── securekeys │ │ └── annotation │ │ ├── SecureKeys.java │ │ └── SecureKey.java │ └── test │ └── java │ └── com │ └── u │ └── securekeys │ └── annotation │ ├── SecureKeyTest.java │ └── SecureKeysTest.java ├── processor ├── .gitignore ├── src │ ├── main │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── services │ │ │ │ └── javax.annotation.processing.Processor │ │ └── java │ │ │ └── com │ │ │ └── u │ │ │ └── securekeys │ │ │ ├── internal │ │ │ ├── Restrictions.java │ │ │ └── Encoder.java │ │ │ └── SecureKeysProcessor.java │ └── test │ │ └── java │ │ └── com │ │ └── u │ │ └── securekeys │ │ ├── internal │ │ ├── EncoderTest.java │ │ └── RestictionsTest.java │ │ ├── mocks │ │ └── Mocks.java │ │ └── SecureKeysProcessorTest.java └── build.gradle ├── testapp ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── values │ │ │ ├── strings.xml │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ └── styles.xml │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ │ └── dimens.xml │ │ └── layout │ │ │ └── activity_main.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── u │ │ └── testapp │ │ └── MainActivity.java ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .github ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE.md ├── ci ├── install_ndk.sh ├── install_android.sh └── install_cmake.sh ├── .gitignore ├── CMakeLists.txt ├── dependencies.gradle ├── gradle.properties ├── circle.yml ├── gradlew.bat ├── README.md └── gradlew /core/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /annotation/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /processor/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /testapp/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':core', ':testapp', ':processor', ':annotation' 2 | -------------------------------------------------------------------------------- /core/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor: -------------------------------------------------------------------------------- 1 | com.u.securekeys.SecureKeysProcessor -------------------------------------------------------------------------------- /testapp/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | testapp 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k0shk0sh/android-api-SecureKeys/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /core/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -keep class com.u.securekeys.ProcessedMap { *; } 2 | 3 | -dontnote org.apache.http.** 4 | -dontnote android.net.http.** -------------------------------------------------------------------------------- /testapp/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k0shk0sh/android-api-SecureKeys/HEAD/testapp/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /testapp/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k0shk0sh/android-api-SecureKeys/HEAD/testapp/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /testapp/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k0shk0sh/android-api-SecureKeys/HEAD/testapp/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /testapp/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k0shk0sh/android-api-SecureKeys/HEAD/testapp/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /testapp/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k0shk0sh/android-api-SecureKeys/HEAD/testapp/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | [Complete] 4 | 5 | ## Issues fixed 6 | 7 | - fixes #issue 8 | 9 | ## Dependencies added 10 | 11 | - \ -------------------------------------------------------------------------------- /annotation/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin:'java' 2 | 3 | sourceCompatibility = JavaVersion.VERSION_1_7 4 | targetCompatibility = JavaVersion.VERSION_1_7 5 | 6 | dependencies { 7 | testCompile testing.junit 8 | } -------------------------------------------------------------------------------- /ci/install_ndk.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ ! -e ~/android-ndk-$1 ]]; then 4 | wget http://dl.google.com/android/repository/android-ndk-$1-linux-x86_64.zip 5 | unzip -qq -d ~ android-ndk-$1-linux-x86_64.zip 6 | fi -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | Makefile 3 | CMakeFiles/ 4 | **/CMakeFiles/ 5 | cmake_install.cmake 6 | CMakeCache.txt 7 | .gradle 8 | /local.properties 9 | .idea/ 10 | *.so 11 | *.dylib 12 | .DS_Store 13 | /build 14 | /captures 15 | .externalNativeBuild 16 | -------------------------------------------------------------------------------- /testapp/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /testapp/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 16 22:50:37 ART 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.5.1-all.zip 7 | -------------------------------------------------------------------------------- /testapp/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /core/src/main/cpp/crypto/base64.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Santiago Aguilera on 3/5/17. 3 | // 4 | 5 | #ifndef SECUREKEYS_BASE64_H 6 | #define SECUREKEYS_BASE64_H 7 | 8 | #include 9 | 10 | static const std::string base64_available_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 11 | "abcdefghijklmnopqrstuvwxyz" 12 | "0123456789+/"; 13 | 14 | std::string base64_decode(std::string &encoded_string); 15 | 16 | #endif //SECUREKEYS_BASE64_H 17 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | project(secure-keys-root) 4 | 5 | if(ANDROID) 6 | message (STATUS "Android OS Detected") 7 | else() 8 | message (STATUS "Not android.") 9 | find_package(JNI REQUIRED) 10 | endif(ANDROID) 11 | 12 | include_directories(${JNI_INCLUDE_DIRS}) 13 | 14 | include(CheckCXXCompilerFlag) 15 | CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) 16 | 17 | add_subdirectory(core/src/main/cpp) -------------------------------------------------------------------------------- /testapp/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /processor/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | sourceCompatibility = JavaVersion.VERSION_1_7 4 | targetCompatibility = JavaVersion.VERSION_1_7 5 | 6 | repositories { 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | compile project(path:':annotation') 12 | compile misc.javapoet 13 | 14 | testCompile testing.junit 15 | testCompile testing.googleTruth 16 | testCompile testing.googleTesting 17 | testCompile files(org.gradle.internal.jvm.Jvm.current().getToolsJar()) 18 | } 19 | -------------------------------------------------------------------------------- /core/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | project(secure-keys) 4 | 5 | link_directories(${JNI_LIBRARIES}) 6 | 7 | add_library(secure-keys 8 | 9 | SHARED 10 | 11 | secure-keys.cpp 12 | crypto/aes.cpp 13 | crypto/base64.cpp 14 | crypto/crypto_wrapper.cpp 15 | 16 | crypto/aes.h 17 | crypto/base64.h 18 | crypto/crypto_wrapper.h) 19 | 20 | set(CMAKE_CXX_FLAGS "-fPIC") 21 | set_target_properties(secure-keys PROPERTIES CMAKE_SHARED_LINKER_FLAGS "-fPIC") -------------------------------------------------------------------------------- /ci/install_android.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ ! -d "/usr/local/android-sdk-linux/platforms/android-25" ]; then 4 | echo y | android update sdk --no-ui --all --filter "android-25" 5 | fi 6 | 7 | if [ ! -d "/usr/local/android-sdk-linux/build-tools/25.0.3" ]; then 8 | echo y | android update sdk --no-ui --all --filter "build-tools-25.0.3" 9 | fi 10 | 11 | if [ ! -d "/usr/local/android_sdk/extras/android/m2repository/com/android/support/support-core-utils/25.4.0" ]; then 12 | echo y | android update sdk --no-ui --all --filter "extra-android-m2repository" 13 | fi 14 | -------------------------------------------------------------------------------- /testapp/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | [FILL THIS OUT: Explain what you did, what you expected to happen, and what actually happens.] 4 | 5 | ### Reproduction 6 | 7 | [FILL THIS OUT: How can we reproduce the bug? Provide URLs to relevant images if possible, or a sample project.] 8 | 9 | ### Solution 10 | 11 | [OPTIONAL: Do you know what needs to be done to address this issue? Ideally, provide a pull request which fixes this issue.] 12 | 13 | ### Additional Information 14 | 15 | * Dependency version: [FILL THIS OUT] 16 | * Dependencies used: [FILL THIS OUT] 17 | * Platform version: [FILL THIS OUT: specific to a particular Android version? Device?] -------------------------------------------------------------------------------- /dependencies.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | supportVersion = '25.4.0' 3 | support = [ 4 | annotations : "com.android.support:support-annotations:$supportVersion", 5 | appcompat : "com.android.support:appcompat-v7:$supportVersion" 6 | ] 7 | 8 | javapoetVersion = '1.8.0' 9 | misc = [ 10 | javapoet : "com.squareup:javapoet:$javapoetVersion" 11 | ] 12 | 13 | junitVersion = '4.4' 14 | googleTesting = '0.11' 15 | googleTruth = '0.33' 16 | testing = [ 17 | junit : "junit:junit:$junitVersion", 18 | googleTesting : "com.google.testing.compile:compile-testing:$googleTesting", 19 | googleTruth : "com.google.truth:truth:$googleTruth" 20 | ] 21 | } -------------------------------------------------------------------------------- /annotation/src/main/java/com/u/securekeys/annotation/SecureKeys.java: -------------------------------------------------------------------------------- 1 | package com.u.securekeys.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Annotation for saving as secure a set of key-value. Add it were you like most. 10 | * Created by saguilera on 3/3/17. 11 | */ 12 | @Target({ ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD}) 13 | @Retention(RetentionPolicy.SOURCE) 14 | public @interface SecureKeys { 15 | 16 | String CLASSPATH = "com.u.securekeys.annotation.SecureKeys"; 17 | 18 | SecureKey[] value(); 19 | 20 | } -------------------------------------------------------------------------------- /annotation/src/main/java/com/u/securekeys/annotation/SecureKey.java: -------------------------------------------------------------------------------- 1 | package com.u.securekeys.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Annotation for saving as secure a key-value. Add it were you like most. 10 | * Created by saguilera on 3/3/17. 11 | */ 12 | @Target({ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD}) 13 | @Retention(RetentionPolicy.SOURCE) 14 | public @interface SecureKey { 15 | 16 | String CLASSPATH = "com.u.securekeys.annotation.SecureKey"; 17 | 18 | // Key and value are mandatory. 19 | String key(); 20 | String value(); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /testapp/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /testapp/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/saguilera/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # So we can test the libs and not the testapp 20 | -keep class com.u.testapp.** { *; } -------------------------------------------------------------------------------- /core/src/main/cpp/crypto/crypto_wrapper.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Santiago Aguilera on 3/5/17. 3 | // 4 | #ifndef SECUREKEYS_CRYPTO_WRAPPER_H 5 | #define SECUREKEYS_CRYPTO_WRAPPER_H 6 | 7 | #include "jni.h" 8 | #include 9 | 10 | #define CRYPTO_WRAPPER_AES_KEY_SIZE 256 11 | 12 | static unsigned char CRYPTO_WRAPPER_AES_IV[16] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 13 | 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; 14 | 15 | static unsigned char CRYPTO_WRAPPER_AES_KEY[32] = { 0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 16 | 0xbe, 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81, 0x1f, 0x35, 0x2c, 17 | 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 18 | 0xf4 }; 19 | 20 | std::string crypto_wrapper_decode(JNIEnv *env, std::string encoded_string); 21 | 22 | #endif //SECUREKEYS_CRYPTO_WRAPPER_H 23 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | environment: 3 | ANDROID_NDK: $HOME/android-ndk-r14 4 | ANDROID_NDK_HOME: $ANDROID_NDK 5 | PATH: $PATH:$ANDROID_NDK 6 | NDK_VERSION: "r14" 7 | GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"' 8 | java: 9 | version: oraclejdk8 10 | 11 | dependencies: 12 | cache_directories: 13 | - ~/android-ndk-$NDK_VERSION 14 | pre: 15 | - bash "ci/install_ndk.sh" "$NDK_VERSION" 16 | - bash "ci/install_cmake.sh" 17 | - bash "ci/install_android.sh" 18 | 19 | test: 20 | override: 21 | - ./gradlew annotation:build --console=plain 22 | - ./gradlew core:assemble core:lint --console=plain # Cant run tests in CI yet, need to find out why on IDE they pass and from terminal cant find the dylib/so files 23 | - ./gradlew processor:build --console=plain 24 | - ./gradlew testapp:build --console=plain 25 | post: 26 | - if [ -d testapp/build/outputs ]; then cp -r testapp/build/outputs $CIRCLE_ARTIFACTS; fi 27 | - if [ -d annotation/build/reports ]; then cp -r annotation/build/reports $CIRCLE_TEST_REPORTS; fi 28 | - if [ -d core/build/reports ]; then cp -r core/build/reports $CIRCLE_TEST_REPORTS; fi 29 | - if [ -d processor/build/reports ]; then cp -r processor/build/reports $CIRCLE_TEST_REPORTS; fi -------------------------------------------------------------------------------- /annotation/src/test/java/com/u/securekeys/annotation/SecureKeyTest.java: -------------------------------------------------------------------------------- 1 | package com.u.securekeys.annotation; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | import static org.junit.Assert.fail; 7 | 8 | /** 9 | * Created by saguilera on 6/16/17. 10 | */ 11 | @SecureKey(key = "test_key", value = "test_value") 12 | public class SecureKeyTest { 13 | 14 | /** 15 | * Test we can generate the clss from the fully-qualified name correctly. 16 | * This is crucial since the annotation processor is hooked via the fully-qualified name, 17 | * if its not a correct one, it wont work 18 | */ 19 | @Test 20 | public void test_ClasspathCorrectlyGeneratesClass() { 21 | try { 22 | Class clazz = Class.forName(SecureKey.CLASSPATH); 23 | Assert.assertTrue(clazz.isAnnotation()); 24 | Assert.assertEquals(clazz, SecureKey.class); 25 | } catch (Exception ex) { 26 | fail(ex.getMessage()); 27 | } 28 | } 29 | 30 | /** 31 | * Test the annotation doesnt get to runtime, if it does then its a major security flaw. 32 | */ 33 | @Test 34 | public void test_AnnotationDoesntGetRetainedForRuntime() { 35 | Assert.assertFalse(SecureKeyTest.class.isAnnotationPresent(SecureKey.class)); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /processor/src/test/java/com/u/securekeys/internal/EncoderTest.java: -------------------------------------------------------------------------------- 1 | package com.u.securekeys.internal; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | /** 7 | * Created by saguilera on 6/18/17. 8 | */ 9 | 10 | public class EncoderTest { 11 | 12 | private final static char[] hexArray = "0123456789ABCDEF".toCharArray(); 13 | 14 | private static String bytesToHex(byte[] bytes) { 15 | char[] hexChars = new char[bytes.length * 2]; 16 | for ( int j = 0; j < bytes.length; j++ ) { 17 | int v = bytes[j] & 0xFF; 18 | hexChars[j * 2] = hexArray[v >>> 4]; 19 | hexChars[j * 2 + 1] = hexArray[v & 0x0F]; 20 | } 21 | return new String(hexChars); 22 | } 23 | 24 | @Test 25 | public void test_EncodeAES() { 26 | // Test array of bytes to crypt 27 | byte[] arr = {0x01, 0x02, 0x03}; 28 | 29 | Encoder encoder = new Encoder(); 30 | byte[] aesarr = encoder.aes(arr); 31 | 32 | Assert.assertEquals("F81B0C071652901E54BA6781CED9589D", bytesToHex(aesarr)); 33 | } 34 | 35 | @Test 36 | public void test_FullCrypt() { 37 | // Test array of bytes to crypt 38 | String value = "test_message"; 39 | 40 | Encoder encoder = new Encoder(); 41 | String encodedValue = encoder.encode(value); 42 | 43 | Assert.assertEquals("MPRSyn8EtW48RrsKM8WFwg==", encodedValue); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /annotation/src/test/java/com/u/securekeys/annotation/SecureKeysTest.java: -------------------------------------------------------------------------------- 1 | package com.u.securekeys.annotation; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | import static org.junit.Assert.fail; 7 | 8 | /** 9 | * Created by saguilera on 6/16/17. 10 | */ 11 | @SecureKeys({ 12 | @SecureKey(key = "test_key_1", value = "test_key_1"), 13 | @SecureKey(key = "test_key_2", value = "test_key_2"), 14 | @SecureKey(key = "test_key_3", value = "test_key_3") 15 | }) 16 | public class SecureKeysTest { 17 | 18 | /** 19 | * Test we can generate the clss from the fully-qualified name correctly. 20 | * This is crucial since the annotation processor is hooked via the fully-qualified name, 21 | * if its not a correct one, it wont work 22 | */ 23 | @Test 24 | public void test_ClasspathCorrectlyGeneratesClass() { 25 | try { 26 | Class clazz = Class.forName(SecureKeys.CLASSPATH); 27 | Assert.assertTrue(clazz.isAnnotation()); 28 | Assert.assertEquals(clazz, SecureKeys.class); 29 | } catch (Exception ex) { 30 | fail(ex.getMessage()); 31 | } 32 | } 33 | 34 | /** 35 | * Test the annotation doesnt get to runtime, if it does then its a major security flaw. 36 | */ 37 | @Test 38 | public void test_AnnotationDoesntGetRetainedForRuntime() { 39 | Assert.assertFalse(SecureKeysTest.class.isAnnotationPresent(SecureKeys.class)); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /testapp/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin:'com.android.application' 2 | apply plugin: 'com.neenbedankt.android-apt' 3 | 4 | android { 5 | compileSdkVersion 25 6 | buildToolsVersion "25.0.3" 7 | 8 | defaultConfig { 9 | applicationId "com.u.testapp" 10 | minSdkVersion 14 11 | targetSdkVersion 25 12 | versionCode 1 13 | versionName "1.0" 14 | } 15 | buildTypes { 16 | debug { 17 | minifyEnabled false 18 | shrinkResources false 19 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 20 | 21 | // Dont put here sensible data, the attacker can see it unencrypted in the BuildConfig file later!! 22 | // If your app has Proguard (and you do remove the BuildConfig fields), then its no problem at all. 23 | buildConfigField "String", "TESTING_VALUE_1", "\"5000\"" 24 | buildConfigField "String", "TESTING_VALUE_2", "\"3.1415\"" 25 | } 26 | release { 27 | minifyEnabled true 28 | shrinkResources true 29 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 30 | 31 | // Dont put here sensible data, the attacker can see it unencrypted in the BuildConfig file later!! 32 | // If your app has Proguard (and you do remove the BuildConfig fields), then its no problem at all. 33 | buildConfigField "String", "TESTING_VALUE_1", "\"5000\"" 34 | buildConfigField "String", "TESTING_VALUE_2", "\"3.1415\"" 35 | } 36 | } 37 | } 38 | 39 | dependencies { 40 | compile support.appcompat 41 | 42 | compile project(path:':core') 43 | apt project(path:':processor') 44 | } 45 | -------------------------------------------------------------------------------- /core/src/main/cpp/crypto/crypto_wrapper.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Santiago Aguilera on 3/5/17. 3 | // 4 | #include "base64.h" 5 | #include "aes.h" 6 | #include 7 | #include "crypto_wrapper.h" 8 | 9 | std::string crypto_wrapper_decode(JNIEnv *env, std::string encoded_string) { 10 | std::string base64_decode_string = base64_decode(encoded_string); 11 | 12 | // Prepare data for aes 13 | unsigned int len = base64_decode_string.length(); 14 | unsigned int src_len = len; 15 | 16 | // Copy data input to ashmem buffer 17 | char *data = &base64_decode_string[0]; 18 | unsigned char *input = (unsigned char *) malloc(src_len); 19 | memset(input, 0, src_len); 20 | memcpy(input, data, len); 21 | 22 | unsigned char * buff = (unsigned char*) malloc(src_len); 23 | if (!buff) { 24 | free(input); 25 | throw "Couldnt assign memmory for buffer inside decode"; 26 | } 27 | memset(buff, src_len, 0); 28 | 29 | // Set key and iv 30 | unsigned int key_schedule[AES_BLOCK_SIZE * 4] = { 0 }; 31 | aes_key_setup(CRYPTO_WRAPPER_AES_KEY, key_schedule, CRYPTO_WRAPPER_AES_KEY_SIZE); 32 | 33 | // Decrypt 34 | aes_decrypt_cbc(input, src_len, buff, key_schedule, CRYPTO_WRAPPER_AES_KEY_SIZE, CRYPTO_WRAPPER_AES_IV); 35 | 36 | // Read padding assigned from last byte, remove it if exist. 37 | unsigned char *ptr = buff; 38 | ptr += (src_len - 1); 39 | unsigned int padding_len = (unsigned int) *ptr; 40 | if (padding_len > 0 && padding_len <= AES_BLOCK_SIZE) { 41 | src_len -= padding_len; 42 | } 43 | 44 | // Interpret it as a string 45 | std::string result(reinterpret_cast(buff), src_len); 46 | 47 | // Release ashmem 48 | free(input); 49 | free(buff); 50 | 51 | return result; 52 | } -------------------------------------------------------------------------------- /core/src/main/cpp/crypto/base64.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Santiago Aguilera on 3/5/17. 3 | // 4 | #include "base64.h" 5 | 6 | static inline bool is_decodable(unsigned char c) { 7 | return (isalnum(c) || (c == '+') || (c == '/')); 8 | } 9 | 10 | std::string base64_decode(std::string &encoded_string) { 11 | int in_len = encoded_string.size(); 12 | int i = 0; 13 | int j = 0; 14 | int in_ = 0; 15 | unsigned char char_array_4[4], char_array_3[3]; 16 | std::string ret; 17 | 18 | while (in_len-- && (encoded_string[in_] != '=') && is_decodable(encoded_string[in_])) { 19 | char_array_4[i++] = encoded_string[in_]; in_++; 20 | if (i ==4) { 21 | for (i = 0; i <4; i++) 22 | char_array_4[i] = base64_available_chars.find(char_array_4[i]); 23 | 24 | char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); 25 | char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); 26 | char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; 27 | 28 | for (i = 0; (i < 3); i++) 29 | ret += char_array_3[i]; 30 | i = 0; 31 | } 32 | } 33 | 34 | if (i) { 35 | for (j = i; j <4; j++) 36 | char_array_4[j] = 0; 37 | 38 | for (j = 0; j <4; j++) 39 | char_array_4[j] = base64_available_chars.find(char_array_4[j]); 40 | 41 | char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); 42 | char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); 43 | char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; 44 | 45 | for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; 46 | } 47 | 48 | return ret; 49 | } -------------------------------------------------------------------------------- /testapp/src/main/java/com/u/testapp/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.u.testapp; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | import android.widget.TextView; 6 | import com.u.securekeys.SecureEnvironment; 7 | import com.u.securekeys.annotation.SecureKey; 8 | import com.u.securekeys.annotation.SecureKeys; 9 | import junit.framework.Assert; 10 | 11 | @SecureKeys({ 12 | @SecureKey(key = "a", value = "e"), 13 | @SecureKey(key = "b", value = "f"), 14 | @SecureKey(key = "c", value = "g"), 15 | @SecureKey(key = "d", value = "h"), 16 | @SecureKey(key = "long_from_BuildConfig", value = BuildConfig.TESTING_VALUE_1), 17 | @SecureKey(key = "double_from_BuildConfig", value = BuildConfig.TESTING_VALUE_2) 18 | }) 19 | public class MainActivity extends AppCompatActivity { 20 | 21 | @Override 22 | @SecureKey(key = "client-secret", value = "aD98E2GEk23TReYds9Zs9zdSdDBi23EAsdq29fXkpsDwp0W+h") 23 | protected void onCreate(Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | setContentView(R.layout.activity_main); 26 | 27 | Assert.assertEquals("aD98E2GEk23TReYds9Zs9zdSdDBi23EAsdq29fXkpsDwp0W+h", SecureEnvironment.getString("client-secret")); 28 | Assert.assertEquals("e", SecureEnvironment.getString("a")); 29 | Assert.assertEquals("f", SecureEnvironment.getString("b")); 30 | Assert.assertEquals("g", SecureEnvironment.getString("c")); 31 | Assert.assertEquals("h", SecureEnvironment.getString("d")); 32 | Assert.assertEquals(Long.valueOf(BuildConfig.TESTING_VALUE_1), Long.valueOf(SecureEnvironment.getLong("long_from_BuildConfig"))); 33 | Assert.assertEquals(Double.valueOf(BuildConfig.TESTING_VALUE_2), SecureEnvironment.getDouble("double_from_BuildConfig")); 34 | 35 | ((TextView) findViewById(R.id.activity_main_key)).setText(SecureEnvironment.getString("client-secret")); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /processor/src/test/java/com/u/securekeys/internal/RestictionsTest.java: -------------------------------------------------------------------------------- 1 | package com.u.securekeys.internal; 2 | 3 | import java.security.InvalidKeyException; 4 | import javax.crypto.Cipher; 5 | import javax.crypto.spec.IvParameterSpec; 6 | import javax.crypto.spec.SecretKeySpec; 7 | import org.junit.Assert; 8 | import org.junit.Test; 9 | 10 | /** 11 | * Created by saguilera on 6/18/17. 12 | */ 13 | 14 | public class RestictionsTest { 15 | 16 | // Initial vector for AES cipher 17 | private static final byte initialVectorBytes[] = new byte[] { 0x04, 0x06, 0x02, 0x01, 0x01, 18 | 0x05, 0x06, 0x0f, 0x0a, 0x09, 0x03, 0x03, 0x03, 0x04, 0x02, 0x0a }; 19 | 20 | // Key used for AES cypher 21 | private static final byte keyBytes[] = new byte[] { 0x60, 0x3d, (byte) 0xe5, 22 | 0x1a, 0x5c, (byte) 0x1a, 0x11, (byte) 0xde, 0x2b, 0x74, 23 | (byte) 0xae, (byte) 0xf0, (byte) 0x8a, 0x7d, 0x77, (byte) 0x83, 24 | 0x13, 0x3d, 0x2c, 0x07, 0x3b, 0x61, 0x08, (byte) 0xda, 0x22, 25 | (byte) 0x9a, 0x11, (byte) 0xa3, 0x09, 0x14, (byte) 0xdf, 26 | (byte) 0xf1 }; 27 | 28 | @Test 29 | public void test_RestrictionsAreRemoved() { 30 | boolean removed = Restrictions.remove(); 31 | 32 | Assert.assertTrue(removed); 33 | 34 | try { 35 | SecretKeySpec key = new SecretKeySpec(keyBytes, "AES"); 36 | 37 | Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 38 | final IvParameterSpec iv = new IvParameterSpec(initialVectorBytes); 39 | 40 | cipher.init(Cipher.ENCRYPT_MODE, key, iv); 41 | byte[] data = cipher.doFinal("test_message".getBytes()); 42 | 43 | Assert.assertNotNull(data); 44 | Assert.assertTrue(data.length > 0); 45 | } catch (InvalidKeyException ex) { 46 | Assert.fail("Shouldnt reach here."); 47 | } catch (Exception ex) { 48 | Assert.fail("Shouldnt reach here."); 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /processor/src/main/java/com/u/securekeys/internal/Restrictions.java: -------------------------------------------------------------------------------- 1 | package com.u.securekeys.internal; 2 | 3 | import java.lang.reflect.Field; 4 | import java.lang.reflect.Modifier; 5 | import java.security.Permission; 6 | import java.security.PermissionCollection; 7 | import java.util.Map; 8 | 9 | /** 10 | * Removes cryptographics restrictions imposed by java jce. You should have it installed in your 11 | * jre/lib/security. But if not (for travis and other CI's running) we disable them. 12 | * 13 | * Created by saguilera on 3/4/17. 14 | */ 15 | class Restrictions { 16 | 17 | private static boolean removed = false; 18 | 19 | public static boolean remove() { 20 | if (removed) return true; 21 | 22 | try { 23 | final Class jceSecurity = Class.forName("javax.crypto.JceSecurity"); 24 | final Class cryptoPermissions = Class.forName("javax.crypto.CryptoPermissions"); 25 | final Class cryptoAllPermission = Class.forName("javax.crypto.CryptoAllPermission"); 26 | 27 | final Field isRestrictedField = jceSecurity.getDeclaredField("isRestricted"); 28 | isRestrictedField.setAccessible(true); 29 | final Field modifiersField = Field.class.getDeclaredField("modifiers"); 30 | modifiersField.setAccessible(true); 31 | modifiersField.setInt(isRestrictedField, isRestrictedField.getModifiers() & ~Modifier.FINAL); 32 | isRestrictedField.set(null, false); 33 | 34 | final Field defaultPolicyField = jceSecurity.getDeclaredField("defaultPolicy"); 35 | defaultPolicyField.setAccessible(true); 36 | final PermissionCollection defaultPolicy = (PermissionCollection) defaultPolicyField.get(null); 37 | 38 | final Field perms = cryptoPermissions.getDeclaredField("perms"); 39 | perms.setAccessible(true); 40 | ((Map) perms.get(defaultPolicy)).clear(); 41 | 42 | final Field instance = cryptoAllPermission.getDeclaredField("INSTANCE"); 43 | instance.setAccessible(true); 44 | defaultPolicy.add((Permission) instance.get(null)); 45 | 46 | return removed = true; 47 | } catch (final Exception e) { 48 | return false; 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /processor/src/test/java/com/u/securekeys/mocks/Mocks.java: -------------------------------------------------------------------------------- 1 | package com.u.securekeys.mocks; 2 | 3 | import com.u.securekeys.annotation.SecureKey; 4 | import com.u.securekeys.annotation.SecureKeys; 5 | 6 | /** 7 | * Created by saguilera on 6/18/17. 8 | */ 9 | public class Mocks { 10 | 11 | /** 12 | * MOCK AN EMPTY CLASS 13 | */ 14 | 15 | public static final String MOCK_EMPTY = "final class EmptyClass {}"; 16 | 17 | /** 18 | * MOCK A CLASS WITH A SINGLE SECURE_KEY 19 | */ 20 | 21 | public static final String MOCK_SECURE_KEY = "" + 22 | "@com.u.securekeys.annotation.SecureKey(key = \"key\", value = \"value\")\n" + 23 | "final class SingleKeyClass {}"; 24 | public static final String MOCK_SECURE_KEY_GEN_FILE = "// Method that retrieves the mapping of the values\n" + 25 | "package com.u.securekeys;\n" + 26 | "\n" + 27 | "import java.lang.String;\n" + 28 | "\n" + 29 | "final class ProcessedMap {\n" + 30 | " public static final String[] retrieve() {\n" + 31 | " String array[] = new String[1];\n" + 32 | " array[0] = \"O0TenKit32Pp/dEAQ9RS6g==;;;;2/lhAK3rkMJXwau5KsBvEA==\";\n" + 33 | " return array;\n" + 34 | " }\n" + 35 | "}"; 36 | 37 | /** 38 | * MOCK A CLASS WITH MORE THAN ONE SECURE_KEY 39 | */ 40 | 41 | public static final String MOCK_SECURE_KEY_MULTIPLE = "" + 42 | " @com.u.securekeys.annotation.SecureKey(key = \"key\", value = \"value\")\n" + 43 | " final class MultipleKeyClass {\n" + 44 | " @com.u.securekeys.annotation.SecureKey(key = \"another\", value = \"anothervalue\")\n" + 45 | " private int field;\n" + 46 | " }"; 47 | public static final String MOCK_SECURE_KEY_MULTIPLE_GEN_FILE = "// Method that retrieves the mapping of the values\n" + 48 | "package com.u.securekeys;\n" + 49 | "\n" + 50 | "import java.lang.String;\n" + 51 | "\n" + 52 | "final class ProcessedMap {\n" + 53 | " public static final String[] retrieve() {\n" + 54 | " String array[] = new String[2];\n" + 55 | " array[0] = \"O0TenKit32Pp/dEAQ9RS6g==;;;;2/lhAK3rkMJXwau5KsBvEA==\";\n" + 56 | " array[1] = \"xGNS15pgulUZCvUQRntc5w==;;;;J2jHPSzh8VORCgND0L9A5g==\";\n" + 57 | " return array;\n" + 58 | " }\n" + 59 | "}"; 60 | 61 | } 62 | -------------------------------------------------------------------------------- /processor/src/main/java/com/u/securekeys/internal/Encoder.java: -------------------------------------------------------------------------------- 1 | package com.u.securekeys.internal; 2 | 3 | import java.security.InvalidKeyException; 4 | import javax.crypto.Cipher; 5 | import javax.crypto.spec.IvParameterSpec; 6 | import javax.crypto.spec.SecretKeySpec; 7 | import javax.xml.bind.DatatypeConverter; 8 | 9 | /** 10 | * Class to encode strings with a given key 11 | * Created by saguilera on 3/3/17. 12 | */ 13 | public class Encoder { 14 | 15 | // Initial vector for AES cipher 16 | private static final byte initialVectorBytes[] = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 17 | 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; 18 | 19 | // Key used for AES cypher 20 | private static final byte keyBytes[] = new byte[] { 0x60, 0x3d, (byte) 0xeb, 21 | 0x10, 0x15, (byte) 0xca, 0x71, (byte) 0xbe, 0x2b, 0x73, 22 | (byte) 0xae, (byte) 0xf0, (byte) 0x85, 0x7d, 0x77, (byte) 0x81, 23 | 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, (byte) 0xd7, 0x2d, 24 | (byte) 0x98, 0x10, (byte) 0xa3, 0x09, 0x14, (byte) 0xdf, 25 | (byte) 0xf4 }; 26 | 27 | public Encoder() {} 28 | 29 | public String encode(String value) { 30 | try { 31 | return DatatypeConverter.printBase64Binary(aes(value.getBytes())); 32 | } catch (Exception e) { 33 | e.printStackTrace(); 34 | throw new RuntimeException("Couldnt encode value: " + value, e); 35 | } 36 | } 37 | 38 | byte[] aes(byte[] content) { 39 | try { 40 | SecretKeySpec key = new SecretKeySpec(keyBytes, "AES"); 41 | 42 | Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 43 | final IvParameterSpec iv = new IvParameterSpec(initialVectorBytes); 44 | 45 | cipher.init(Cipher.ENCRYPT_MODE, key, iv); 46 | return cipher.doFinal(content); 47 | } catch (InvalidKeyException e) { 48 | System.out.println("Please install JCE's Unlimited Strength Policies for next compilation"); 49 | if (Restrictions.remove()) 50 | return aes(content); 51 | else throw new RuntimeException("No JCE's policies installed + couldnt bypass them", e); 52 | } catch (Exception e) { 53 | e.printStackTrace(); 54 | throw new RuntimeException("Unknown exception while trying to encript with aes", e); 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /core/src/main/cpp/crypto/aes.h: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Filename: aes.h 3 | * Author: Brad Conte (brad AT bradconte.com) 4 | * Copyright: 5 | * Disclaimer: This code is presented "as is" without any guarantees. 6 | * Details: Defines the API for the corresponding AES implementation. 7 | * 8 | * Edited by: Santiago Aguilera 9 | *********************************************************************/ 10 | 11 | #ifndef SECUREKEYS_AES_H 12 | #define SECUREKEYS_AES_H 13 | 14 | /*************************** HEADER FILES ***************************/ 15 | #include 16 | #include 17 | 18 | /****************************** MACROS ******************************/ 19 | #define AES_BLOCK_SIZE 16 // AES operates on 16 bytes at a time 20 | 21 | /**************************** DATA TYPES ****************************/ 22 | typedef unsigned char BYTE; // 8-bit byte 23 | typedef unsigned int WORD; // 32-bit word, change to "long" for 16-bit machines 24 | 25 | /*********************** FUNCTION DECLARATIONS **********************/ 26 | /////////////////// 27 | // AES 28 | /////////////////// 29 | // Key setup must be done before any AES decryption function can be used. 30 | void aes_key_setup(const BYTE key[], // The key, must be 128, 192, or 256 bits 31 | WORD w[], // Output key schedule to be used later 32 | int keysize); // Bit length of the key, 128, 192, or 256 33 | 34 | void aes_decrypt(const BYTE in[], // 16 bytes of ciphertext 35 | BYTE out[], // 16 bytes of plaintext 36 | const WORD key[], // From the key setup 37 | int keysize); // Bit length of the key, 128, 192, or 256 38 | 39 | /////////////////// 40 | // AES - CBC 41 | /////////////////// 42 | int aes_decrypt_cbc(const BYTE in[], // Plaintext 43 | size_t in_len, // Must be a multiple of AES_BLOCK_SIZE 44 | BYTE out[], // Ciphertext, same length as plaintext! 45 | const WORD key[], // From the key setup 46 | int keysize, // Bit length of the key, 128, 192 or 256 47 | const BYTE iv[]); // IV, must be AES_BLOCK_SIZE bytes long 48 | 49 | #endif //SECUREKEYS_AES_H 50 | -------------------------------------------------------------------------------- /core/src/main/java/com/u/securekeys/SecureEnvironment.java: -------------------------------------------------------------------------------- 1 | package com.u.securekeys; 2 | 3 | import android.support.annotation.Keep; 4 | import android.support.annotation.NonNull; 5 | import java.lang.reflect.Method; 6 | 7 | /** 8 | * Bridge between native and java for accessing secure keys. 9 | * 10 | * Created by saguilera on 3/3/17. 11 | */ 12 | public final class SecureEnvironment { 13 | 14 | private static final String ENV_LIBRARY_NAME = "secure-keys"; 15 | static final String ENV_PROCESSED_MAP_NAME = "com.u.securekeys.ProcessedMap"; 16 | static final String ENV_PROCESSED_MAP_METHOD = "retrieve"; 17 | 18 | private static final long NAN_LONG = -1; 19 | private static final String NAN_STRING = ""; 20 | 21 | private static boolean initialized; 22 | 23 | static { 24 | System.loadLibrary(ENV_LIBRARY_NAME); 25 | 26 | try { 27 | tryNativeInit(); 28 | initialized = true; 29 | } catch (Exception e) { 30 | initialized = false; 31 | } 32 | } 33 | 34 | private SecureEnvironment() throws IllegalAccessException { 35 | throw new IllegalAccessException("This object cant be instantiated"); 36 | } 37 | 38 | @Keep 39 | private static void tryNativeInit() throws Exception { 40 | Class clazz = Class.forName(ENV_PROCESSED_MAP_NAME); 41 | Method method = clazz.getDeclaredMethod(ENV_PROCESSED_MAP_METHOD); 42 | method.setAccessible(true); 43 | nativeInit((String[]) method.invoke(null)); 44 | } 45 | 46 | public static @NonNull String getString(@NonNull String key) { 47 | if (!initialized || key.isEmpty()) { 48 | return NAN_STRING; 49 | } 50 | 51 | return nativeGetString(key); 52 | } 53 | 54 | public static long getLong(@NonNull String key) { 55 | String value = getString(key); 56 | 57 | if (!initialized || value.isEmpty()) { 58 | return NAN_LONG; 59 | } 60 | 61 | return Long.valueOf(value); 62 | } 63 | 64 | public static double getDouble(@NonNull String key) { 65 | String value = getString(key); 66 | 67 | if (!initialized || value.isEmpty()) { 68 | return NAN_LONG; 69 | } 70 | 71 | return Double.valueOf(value); 72 | } 73 | 74 | @Keep 75 | private static native String nativeGetString(String key); 76 | @Keep 77 | private static native void nativeInit(String[] array); 78 | 79 | } 80 | -------------------------------------------------------------------------------- /core/src/main/cpp/secure-keys.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "crypto/crypto_wrapper.h" 7 | 8 | #define _separator ";;;;" 9 | #define _default_response "" 10 | 11 | #define decode crypto_wrapper_decode 12 | 13 | std::map mapVals; 14 | 15 | extern "C" { 16 | JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved); 17 | JNIEXPORT jstring JNICALL Java_com_u_securekeys_SecureEnvironment_nativeGetString(JNIEnv *env, jclass instance, jstring key); 18 | JNIEXPORT void JNICALL Java_com_u_securekeys_SecureEnvironment_nativeInit(JNIEnv *env, jclass instance, jobjectArray array); 19 | }; 20 | 21 | JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { 22 | JNIEnv* env; 23 | if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { 24 | return JNI_ERR; 25 | } 26 | 27 | return JNI_VERSION_1_6; 28 | } 29 | 30 | JNIEXPORT void JNICALL Java_com_u_securekeys_SecureEnvironment_nativeInit 31 | (JNIEnv *env, jclass instance, jobjectArray array) { 32 | int stringCount = env->GetArrayLength(array); 33 | 34 | for (int i = 0; i < stringCount; i++) { 35 | jstring string = (jstring) (env->GetObjectArrayElement(array, i)); 36 | const char *rawString = env->GetStringUTFChars(string, 0); 37 | 38 | std::string keyval(rawString); 39 | unsigned long separator = keyval.find(_separator); 40 | if (separator != std::string::npos) { 41 | mapVals[keyval.substr(0, separator)] = keyval.substr(separator + 4); 42 | } 43 | 44 | if (string != NULL) { 45 | (env)->ReleaseStringUTFChars(string, rawString); 46 | } 47 | env->DeleteLocalRef(string); 48 | } 49 | 50 | env->DeleteLocalRef(array); 51 | } 52 | 53 | JNIEXPORT jstring JNICALL Java_com_u_securekeys_SecureEnvironment_nativeGetString 54 | (JNIEnv *env, jclass instance, jstring key) { 55 | const char *rawString = env->GetStringUTFChars(key, 0); 56 | std::string paramKey(rawString); 57 | for(std::pair const &pair : mapVals) { 58 | if (paramKey.compare(decode(env, pair.first)) == 0) { 59 | env->ReleaseStringUTFChars(key, rawString); 60 | env->DeleteLocalRef(key); 61 | return (env)->NewStringUTF(decode(env, pair.second).c_str()); 62 | } 63 | } 64 | 65 | env->ReleaseStringUTFChars(key, rawString); 66 | env->DeleteLocalRef(key); 67 | 68 | return (env)->NewStringUTF(_default_response); 69 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /ci/install_cmake.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Credits to -> https://github.com/Commit451 4 | 5 | # install CMake for Android builds. Assumes that you have ANDROID_HOME set properly 6 | # Bash scripts are not my thang, so most of these techniques was taken from stackoverflow 7 | PACKAGE_XML_URL="https://github.com/Commit451/android-cmake-installer/releases/download/1.0.0/package.xml" 8 | VERSION_MAJOR="3" 9 | VERSION_MINOR="6" 10 | VERSION_MICRO="3155560" 11 | VERSION=${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_MICRO} 12 | # can also be darwin or windows (but not really cause .sh) 13 | PLATFORM="linux" 14 | # A POSIX variable 15 | # Reset in case getopts has been used previously in the shell. 16 | OPTIND=1 17 | 18 | # Initialize our own variables: 19 | DEBUG=false 20 | 21 | # : next to each one that takes variables 22 | while getopts ":dp:v:" opt; do 23 | case $opt in 24 | d) 25 | DEBUG=true 26 | ;; 27 | p) 28 | PLATFORM=$OPTARG 29 | if [[ "$PLATFORM" != "linux" && "$PLATFORM" != "darwin" ]] ; then 30 | echo "Invalid platform: $PLATFORM" 31 | echo "Options are \"darwin\" (mac) or \"linux\"" 32 | exit 33 | fi 34 | ;; 35 | v) 36 | VERSION=$OPTARG 37 | #splits the version by the . http://stackoverflow.com/a/29903172/895797 38 | # evaluate command and assign to var http://stackoverflow.com/a/2559087/895797 39 | VERSION_MAJOR=$(echo "$VERSION" | cut -d "." -f 1) 40 | VERSION_MINOR=$(echo "$VERSION" | cut -d "." -f 2) 41 | VERSION_MICRO=$(echo "$VERSION" | cut -d "." -f 3) 42 | ;; 43 | \?) 44 | echo "Invalid option: -$OPTARG" 45 | ;; 46 | esac 47 | done 48 | 49 | shift $((OPTIND-1)) 50 | 51 | [ "$1" = "--" ] && shift 52 | if [ "$DEBUG" = true ] ; then 53 | echo 'Debug enabled. Prepare for lots of printing' 54 | echo "Platform: $PLATFORM" 55 | echo "Version: $VERSION" 56 | echo "Version Major: $VERSION_MAJOR" 57 | echo "Version Minor: $VERSION_MINOR" 58 | echo "Version Micro: $VERSION_MICRO" 59 | fi 60 | 61 | NAME="cmake-${VERSION}-${PLATFORM}-x86_64" 62 | wget https://dl.google.com/android/repository/${NAME}.zip 63 | 64 | if [ ! -f ${NAME}.zip ]; then 65 | echo "CMake version not found on server. Make sure your version is formatted like 3.6.1234" 66 | exit 67 | fi 68 | 69 | DIRECTORY=${ANDROID_HOME}/cmake/${VERSION} 70 | mkdir -p ${DIRECTORY} 71 | unzip ${NAME}.zip -d ${DIRECTORY} 72 | rm ${NAME}.zip 73 | # Now, in order to trick gradle into believing that we have installed 74 | # through the official means, we need to include a package.xml file 75 | wget ${PACKAGE_XML_URL} 76 | sed -i -- 's/CMAKE_VERSION_COMPLETE/'"$VERSION"'/g' package.xml 77 | sed -i -- 's/CMAKE_VERSION_MAJOR/'"$VERSION_MAJOR"'/g' package.xml 78 | sed -i -- 's/CMAKE_VERSION_MINOR/'"$VERSION_MINOR"'/g' package.xml 79 | sed -i -- 's/CMAKE_VERSION_MICRO/'"$VERSION_MICRO"'/g' package.xml 80 | mv package.xml ${DIRECTORY} -------------------------------------------------------------------------------- /processor/src/test/java/com/u/securekeys/SecureKeysProcessorTest.java: -------------------------------------------------------------------------------- 1 | package com.u.securekeys; 2 | 3 | import com.google.testing.compile.Compilation; 4 | import com.google.testing.compile.JavaFileObjects; 5 | import com.u.securekeys.mocks.Mocks; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.Locale; 9 | import javax.tools.Diagnostic; 10 | import javax.tools.JavaFileObject; 11 | import org.junit.Assert; 12 | import org.junit.Test; 13 | 14 | import static com.google.testing.compile.CompilationSubject.assertThat; 15 | import static com.google.testing.compile.Compiler.javac; 16 | 17 | public class SecureKeysProcessorTest { 18 | 19 | @Test 20 | public void test_EmptyClassProcessedOk() { 21 | Compilation compilation = javac() 22 | .withProcessors(new SecureKeysProcessor()) 23 | .compile(JavaFileObjects.forSourceString("EmptyClass", Mocks.MOCK_EMPTY)); 24 | 25 | validate(compilation); 26 | Assert.assertTrue(stripClasses(compilation.generatedFiles()).isEmpty()); 27 | } 28 | 29 | @Test 30 | public void test_SingleSecureKeyProcessedOk() { 31 | Compilation compilation = javac() 32 | .withProcessors(new SecureKeysProcessor()) 33 | .compile(JavaFileObjects.forSourceString("SingleKeyClass", Mocks.MOCK_SECURE_KEY)); 34 | 35 | validate(compilation); 36 | Assert.assertEquals(1, stripClasses(compilation.generatedFiles()).size()); 37 | 38 | assertThat(compilation) 39 | .generatedSourceFile("com/u/securekeys/ProcessedMap") 40 | .hasSourceEquivalentTo(JavaFileObjects.forSourceString("ProcessedMap", Mocks.MOCK_SECURE_KEY_GEN_FILE)); 41 | } 42 | 43 | @Test 44 | public void test_MultipleSecureKeyProcessedOk() { 45 | Compilation compilation = javac() 46 | .withProcessors(new SecureKeysProcessor()) 47 | .compile(JavaFileObjects.forSourceString("MultipleKeyClass", Mocks.MOCK_SECURE_KEY_MULTIPLE)); 48 | 49 | validate(compilation); 50 | Assert.assertEquals(1, stripClasses(compilation.generatedFiles()).size()); 51 | 52 | assertThat(compilation) 53 | .generatedSourceFile("com/u/securekeys/ProcessedMap") 54 | .hasSourceEquivalentTo(JavaFileObjects.forSourceString("ProcessedMap", Mocks.MOCK_SECURE_KEY_MULTIPLE_GEN_FILE)); 55 | } 56 | 57 | void validate(Compilation compilation) { 58 | // Check that it succeded 59 | assertThat(compilation).succeeded(); 60 | 61 | // Check that only the warning of the classpath conjunction of source is shown, no more. 62 | for (Diagnostic diagnostic : compilation.warnings()) { 63 | if (!diagnostic.getMessage(Locale.ENGLISH).contains("Supported source version 'RELEASE_7' from annotation processor")) { 64 | Assert.fail("Warnings found in the compilation: " + diagnostic.getMessage(Locale.ENGLISH)); 65 | } 66 | } 67 | } 68 | 69 | List stripClasses(List files) { 70 | List newList = new ArrayList<>(); 71 | for (JavaFileObject fileObject : files) { 72 | if (fileObject.getKind() != JavaFileObject.Kind.CLASS) { 73 | newList.add(fileObject); 74 | } 75 | } 76 | return newList; 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /core/src/test/java/com/u/securekeys/SecureEnvironmentTest.java: -------------------------------------------------------------------------------- 1 | package com.u.securekeys; 2 | 3 | import com.u.securekeys.annotation.SecureKey; 4 | import com.u.securekeys.annotation.SecureKeys; 5 | import java.lang.reflect.Field; 6 | import java.lang.reflect.Method; 7 | import org.junit.Assert; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | 11 | import static com.u.securekeys.SecureEnvironment.ENV_PROCESSED_MAP_METHOD; 12 | import static com.u.securekeys.SecureEnvironment.ENV_PROCESSED_MAP_NAME; 13 | import static org.junit.Assert.fail; 14 | 15 | /** 16 | * This test suite can only be run for the moment from the IDE, since it adds all the necessary classpaths for using the 17 | * java.library.path when loading a JNI library. From console its still impossible to test, so when coding 18 | * please ensure to test the full suite from the IDE 19 | */ 20 | @SecureKeys({ 21 | @SecureKey(key = "test_key_string", value = "A simple string"), 22 | @SecureKey(key = "test_key_long", value = "1234567890987654321"), 23 | @SecureKey(key = "test_key_double", value = "0.98765432123456789") 24 | }) 25 | public class SecureEnvironmentTest { 26 | 27 | @Before 28 | public void setUp() { 29 | SecureEnvironment.getString("nothing"); 30 | } 31 | 32 | /** 33 | * Check we can initialize the native library correctly 34 | */ 35 | @Test 36 | public void test_JNIInitialized() { 37 | try { 38 | Field initializedField = SecureEnvironment.class.getDeclaredField("initialized"); 39 | initializedField.setAccessible(true); 40 | Boolean initialized = initializedField.getBoolean(null); 41 | Assert.assertTrue(initialized); 42 | } catch (Exception ex) { 43 | fail(ex.getMessage()); 44 | } 45 | } 46 | 47 | /** 48 | * Test the processed map with the mappings exists. 49 | * Dont check if it has garbage or the values, that is a problem of the processor module. 50 | * We do have a problem if we cant find the mappings. 51 | */ 52 | @Test 53 | public void test_ProcessedMapExists() { 54 | try { 55 | Class clazz = Class.forName(ENV_PROCESSED_MAP_NAME); 56 | Method method = clazz.getDeclaredMethod(ENV_PROCESSED_MAP_METHOD); 57 | method.setAccessible(true); 58 | String[] mappings = (String[]) method.invoke(null); 59 | 60 | Assert.assertEquals("There are only 3 keys, so something went wrong", 3, mappings.length); 61 | } catch (Exception ex) { 62 | fail(ex.getMessage()); 63 | } 64 | } 65 | 66 | /** 67 | * Test the accessors to values 68 | */ 69 | @Test 70 | public void test_GetString() { 71 | Assert.assertEquals("Values werent the same", "A simple string", SecureEnvironment.getString("test_key_string")); 72 | } 73 | 74 | /** 75 | * Test the accessors to values 76 | */ 77 | @Test 78 | public void test_GetLong() { 79 | Assert.assertEquals("Values werent the same", 1234567890987654321L, SecureEnvironment.getLong("test_key_long")); 80 | } 81 | 82 | /** 83 | * Test the accessors to values 84 | */ 85 | @Test 86 | public void test_GetDouble() { 87 | Assert.assertEquals("Values werent the same", 0.98765432123456789D, SecureEnvironment.getDouble("test_key_double"), 0.00000000000000001D); 88 | } 89 | 90 | } -------------------------------------------------------------------------------- /core/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.3" 6 | 7 | defaultConfig { 8 | minSdkVersion 14 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | consumerProguardFile 'proguard-rules.pro' 13 | externalNativeBuild { 14 | cmake { 15 | cppFlags "-std=c++11 -fexceptions" 16 | } 17 | } 18 | } 19 | 20 | externalNativeBuild { 21 | cmake { 22 | path "src/main/cpp/CMakeLists.txt" 23 | } 24 | } 25 | } 26 | 27 | /** 28 | * Since we cant add our path to the java.library.path (.so/dylib lookup places) 29 | * because gradle will eventually overwrite them with the defaults, we add it 30 | * to one of those our lib and then delete it 31 | */ 32 | task setupJNILibrary() { 33 | doLast { 34 | def extension = org.apache.tools.ant.taskdefs.condition.Os.isFamily( 35 | org.apache.tools.ant.taskdefs.condition.Os.FAMILY_MAC) ? 'dylib' : 'so' 36 | def soFilePath = "./build/core/src/main/cpp/libsecure-keys.${extension}"; 37 | File inputFile = new File(soFilePath) 38 | if (inputFile.exists()) { 39 | def possiblePlaces = System.properties.'java.library.path'.tokenize(':') 40 | possiblePlaces.each { dest -> 41 | File destFile = new File("${dest}/libsecure-keys.${extension}") 42 | 43 | try { 44 | if (destFile.createNewFile() || destFile.exists()) { 45 | destFile << inputFile.bytes 46 | println destFile.absolutePath 47 | } 48 | } catch (IOException ioEx) { 49 | // Silent. The dir is probably protected for write access 50 | } 51 | } 52 | } else { 53 | throw new GradleException(".${extension} file not found. Please run from the root-project folder:\n" + 54 | "1. mkdir build && cd build\n" + 55 | "2. cmake ..\n" + 56 | "3. make\n" + 57 | "If you have already done this. Then the classloader paths are all write protected, please copy this file on your own to one of this places.\n" + 58 | "file: ./build/core/src/main/cpp/libsecure-keys.${extension}\n" + 59 | "places: ${System.properties.'java.library.path'}") 60 | } 61 | } 62 | } 63 | 64 | task teardownJNILibrary() { 65 | doLast { 66 | def extension = org.apache.tools.ant.taskdefs.condition.Os.isFamily( 67 | org.apache.tools.ant.taskdefs.condition.Os.FAMILY_MAC) ? 'dylib' : 'so' 68 | def possiblePlaces = System.properties.'java.library.path'.tokenize(':') 69 | for (def place : possiblePlaces) { 70 | File file = new File("${place}/libsecure-keys.${extension}") 71 | if (file.exists()) { 72 | file.delete() 73 | } 74 | } 75 | } 76 | } 77 | 78 | afterEvaluate { 79 | android.buildTypes.all { buildType -> 80 | def buildTypeTask = tasks["compile${buildType.name.capitalize()}UnitTestSources"] 81 | buildTypeTask.dependsOn project.tasks.setupJNILibrary 82 | project.tasks.teardownJNILibrary.shouldRunAfter buildTypeTask 83 | } 84 | } 85 | 86 | dependencies { 87 | compile project(path:':annotation') 88 | 89 | compile support.annotations 90 | 91 | testCompile testing.junit 92 | testAnnotationProcessor project(path:':processor') 93 | } 94 | -------------------------------------------------------------------------------- /processor/src/main/java/com/u/securekeys/SecureKeysProcessor.java: -------------------------------------------------------------------------------- 1 | package com.u.securekeys; 2 | 3 | import com.squareup.javapoet.JavaFile; 4 | import com.squareup.javapoet.MethodSpec; 5 | import com.squareup.javapoet.TypeSpec; 6 | import com.u.securekeys.annotation.SecureKey; 7 | import com.u.securekeys.annotation.SecureKeys; 8 | import com.u.securekeys.internal.Encoder; 9 | import java.io.IOException; 10 | import java.util.ArrayList; 11 | import java.util.Arrays; 12 | import java.util.List; 13 | import java.util.Set; 14 | import javax.annotation.processing.AbstractProcessor; 15 | import javax.annotation.processing.RoundEnvironment; 16 | import javax.annotation.processing.SupportedAnnotationTypes; 17 | import javax.annotation.processing.SupportedSourceVersion; 18 | import javax.lang.model.SourceVersion; 19 | import javax.lang.model.element.Element; 20 | import javax.lang.model.element.Modifier; 21 | import javax.lang.model.element.TypeElement; 22 | 23 | @SupportedAnnotationTypes({ SecureKey.CLASSPATH, SecureKeys.CLASSPATH }) 24 | @SupportedSourceVersion(SourceVersion.RELEASE_7) 25 | public class SecureKeysProcessor extends AbstractProcessor { 26 | 27 | /** 28 | * Remember that the SecureKeys.java inside core references this class! 29 | */ 30 | private static final String CLASS_NAME = "ProcessedMap"; 31 | private static final String CLASS_CLASSPATH = "com.u.securekeys"; 32 | 33 | @Override 34 | public boolean process(final Set set, final RoundEnvironment roundEnvironment) { 35 | List annotations = flattenElements( 36 | roundEnvironment.getElementsAnnotatedWith(SecureKey.class), 37 | roundEnvironment.getElementsAnnotatedWith(SecureKeys.class) 38 | ); 39 | 40 | MethodSpec.Builder retrieveMethodBuilder = MethodSpec.methodBuilder("retrieve") 41 | .addModifiers(Modifier.FINAL, Modifier.PUBLIC, Modifier.STATIC) 42 | .returns(String[].class) 43 | .addStatement("String array[] = new String[" + annotations.size() + "]"); 44 | 45 | int counter = 0; 46 | Encoder encoder = new Encoder(); 47 | for (SecureKey annotation : annotations) { 48 | String key = encoder.encode(annotation.key()); 49 | String value = encoder.encode(annotation.value()); 50 | 51 | retrieveMethodBuilder.addStatement("array[" + counter + "] = \"" + key + ";;;;" + 52 | value + "\""); 53 | 54 | ++counter; 55 | } 56 | 57 | retrieveMethodBuilder.addStatement("return array"); 58 | 59 | TypeSpec createdClass = TypeSpec.classBuilder(CLASS_NAME) 60 | .addModifiers(Modifier.FINAL) 61 | .addMethod(retrieveMethodBuilder.build()) 62 | .build(); 63 | 64 | JavaFile javaFile = JavaFile.builder(CLASS_CLASSPATH, createdClass) 65 | .addFileComment("Method that retrieves the mapping of the values") 66 | .build(); 67 | 68 | try { 69 | javaFile.writeTo(processingEnv.getFiler()); 70 | } catch (IOException e) { /* Silent. */ } 71 | 72 | return true; 73 | } 74 | 75 | private List flattenElements(Set secureKeyElements, 76 | Set secureKeysElements) { 77 | List result = new ArrayList<>(); 78 | 79 | for (Element element : secureKeyElements) { 80 | result.add(element.getAnnotation(SecureKey.class)); 81 | } 82 | 83 | for (Element element : secureKeysElements) { 84 | result.addAll(Arrays.asList(element.getAnnotation(SecureKeys.class).value())); 85 | } 86 | 87 | return result; 88 | } 89 | 90 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SecureKeys 2 | 3 | [![CircleCI](https://circleci.com/gh/saantiaguilera/android-api-SecureKeys/tree/develop.svg?style=svg)](https://circleci.com/gh/saantiaguilera/android-api-SecureKeys/tree/develop) 4 | 5 | A tiny lib (Less than 10 methods) to store constants where attackers will have a harder time to find. 6 | 7 | ### Description 8 | 9 | This library uses an annotationProcessor to store the constants in a new file (where the constants are encrypted), and via JNI it will later retrieve them decoding them inside the `.o` file. 10 | 11 | This way the attackers cant know the encoding system (because its inside the annotation processor), neither the decoding (unless they read the assembly code of the `.o` file). 12 | 13 | **Note:** They can still "find" the class with the crypted constants or do a heapdump of the map inside the `.o` file. But since its encrypted they will have a harder time figuring the constants out. 14 | 15 | ### Relevant notes 16 | 17 | - The annotations used for the processor are removed in compile time, so they wont be shipped to the apk :) 18 | - The generated class by the apt will be shipped inside your apk, but all the constants will be already encrypted. (attacker could still do a heapdump to know the encrypted constants) 19 | - Current encryption system is AES (CBC + Padding5) + Base64. AES key and vector are private and local to the repository (planning to make them customizable) 20 | 21 | ### Usage 22 | 23 | **I havent uploaded yet to bintray, so currently theres no way :poop:** -> Soon will be :) 24 | 25 | For the moment, I highly recommend you to add this project manually so you can also change for a custom initial_vector and aes_key. 26 | 27 | Annotate secure stuff wherever you like as: 28 | ```Java 29 | @SecureKeys({ 30 | @SecureKey(key = "client_secret", value = "my_client_secret..."), 31 | @SecureKey(key = "another_one_here", value = "...") 32 | }) 33 | class MyClass { 34 | 35 | @SecureKey(key = "or_here_a_single_one", value = "...") 36 | public void myMethod() {} 37 | 38 | } 39 | ``` 40 | This annotations wont be shipped with the apk, so fear not my friend :) 41 | 42 | Possible places for annotating are: 43 | - Classes 44 | - Constructors 45 | - Fields 46 | - Methods 47 | 48 | Thats all. Whenever you plan on using them simply call one of: 49 | ```Java 50 | SecureEnvironment.getString("client_secret"); 51 | SecureEnvironment.getLong("crash_tracking_system_user_id"); 52 | SecureEnvironment.getDouble("time_for_destroying_the_world"); 53 | ``` 54 | 55 | ### Code generation 56 | 57 | Generated code for this 2 annotations: 58 | ```Java 59 | @SecureKey(key = "client-secret", value = "aD98E2GEk23TReYds9Zs9zdSdDBi23EAsdq29fXkpsDwp0W+h") 60 | @SecureKey(key = "key22", value = "value2") 61 | ``` 62 | Will look like this: 63 | ```Java 64 | ... 65 | L1 66 | LINENUMBER 9 L1 67 | ALOAD 0 68 | ICONST_0 69 | // This string is "client-secret" 70 | LDC "c+Ciy4ZAfaCkSK3aBgVCDg==;;;;jUvAlWYtbJJXOB5PWy1NMsgtAjOcBYdZpSgWcvBjnfwXtmyCsMFnPHeM4CrLdYPO2xmk2IAnOGhlsVn55eV6wA==" 71 | AASTORE 72 | L2 73 | LINENUMBER 10 L2 74 | ALOAD 0 75 | ICONST_1 76 | // This string is "key22" 77 | LDC "zNgp44YJZLppymmiBdEL8A==;;;;Hk6WGAWtAPtVZYENYVUhkg==" 78 | AASTORE 79 | L3 80 | ... 81 | ``` 82 | 83 | ### Proguard 84 | 85 | Currently the library supports transitively Proguard, so just by adding the aar you should be safe :) 86 | 87 | ### Benchmarks 88 | 89 | Benchmark was ran on a Samsung Fame Lite (pretty old phone): 90 | * Android 4.1.2 91 | * CPU 1Ghz Single-Core ARM Cortex-A9 92 | * 512MB RAM 93 | * ISA ARMv7 94 | 95 | There were 5000 different keys encoded, and was tested 100 times the initializing of the library and the retrieval of a key (the 5000th) 96 | 97 | **Time to initialize the library** (ms): 209 98 | 99 | **Time to retrieve the 5000th key** (ms): 144 100 | 101 | ### Contributing 102 | 103 | Fork and submit a PR! 104 | 105 | Modules: 106 | - annotation: Provides annotations that are used by the processor 107 | - core: The interaction of the user. Decrypts are done using C++, a java bridge is used for asking them 108 | - processor: Custom APT that handles the annotations and produces crypted key/values (that can be handled by the c++ lib) 109 | - testapp: Test application for testing all of the above 110 | 111 | Relevant notes for developing it: 112 | - JNI/Java bridge Tests are not supported by the platform so a "proxy" was created for giving it compatibility. Since this is not crucial for the project, it only works from the IDE, not from console (I should add all the classpaths dynamically before running the JUnit Starter. **Please ensure ALL the tests of the `:core` module pass from the Android Studio IDE** 113 | - JNI Tests are not supported out of the box, so there are no tests for it. A PR is welcome adding them (using cppunit or some tool ofc) 114 | 115 | ### Missing features: 116 | - [ ] Let the consumer set their own AES key (this is tricky, key shouldnt be exposed to APK but should be visible for apt AND JNI) 117 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 165 | if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then 166 | cd "$(dirname "$0")" 167 | fi 168 | 169 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 170 | -------------------------------------------------------------------------------- /core/src/main/cpp/crypto/aes.cpp: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * Filename: aes.cpp 3 | * Author: Brad Conte (brad AT bradconte.com) 4 | * Copyright: 5 | * Disclaimer: This code is presented "as is" without any guarantees. 6 | * Details: This code is the implementation of the AES algorithm and 7 | the CTR, CBC, and CCM modes of operation it can be used in. 8 | AES is, specified by the NIST in in publication FIPS PUB 197, 9 | availible at: 10 | * http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf . 11 | The CBC and CTR modes of operation are specified by 12 | NIST SP 800-38 A, available at: 13 | * http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf . 14 | The CCM mode of operation is specified by NIST SP80-38 C, available at: 15 | * http://csrc.nist.gov/publications/nistpubs/800-38C/SP800-38C_updated-July20_2007.pdf 16 | * 17 | * 18 | * Edited by: Santiago Aguilera 19 | *********************************************************************/ 20 | 21 | /*************************** HEADER FILES ***************************/ 22 | #include 23 | #include 24 | #include "aes.h" 25 | 26 | /****************************** MACROS ******************************/ 27 | // The least significant byte of the word is rotated to the end. 28 | #define KE_ROTWORD(x) (((x) << 8) | ((x) >> 24)) 29 | 30 | #define TRUE 1 31 | #define FALSE 0 32 | 33 | /**************************** VARIABLES *****************************/ 34 | // This is the specified AES SBox. To look up a substitution value, put the first 35 | // nibble in the first index (row) and the second nibble in the second index (column). 36 | static const BYTE aes_sbox[16][16] = { 37 | {0x63,0x7C,0x77,0x7B,0xF2,0x6B,0x6F,0xC5,0x30,0x01,0x67,0x2B,0xFE,0xD7,0xAB,0x76}, 38 | {0xCA,0x82,0xC9,0x7D,0xFA,0x59,0x47,0xF0,0xAD,0xD4,0xA2,0xAF,0x9C,0xA4,0x72,0xC0}, 39 | {0xB7,0xFD,0x93,0x26,0x36,0x3F,0xF7,0xCC,0x34,0xA5,0xE5,0xF1,0x71,0xD8,0x31,0x15}, 40 | {0x04,0xC7,0x23,0xC3,0x18,0x96,0x05,0x9A,0x07,0x12,0x80,0xE2,0xEB,0x27,0xB2,0x75}, 41 | {0x09,0x83,0x2C,0x1A,0x1B,0x6E,0x5A,0xA0,0x52,0x3B,0xD6,0xB3,0x29,0xE3,0x2F,0x84}, 42 | {0x53,0xD1,0x00,0xED,0x20,0xFC,0xB1,0x5B,0x6A,0xCB,0xBE,0x39,0x4A,0x4C,0x58,0xCF}, 43 | {0xD0,0xEF,0xAA,0xFB,0x43,0x4D,0x33,0x85,0x45,0xF9,0x02,0x7F,0x50,0x3C,0x9F,0xA8}, 44 | {0x51,0xA3,0x40,0x8F,0x92,0x9D,0x38,0xF5,0xBC,0xB6,0xDA,0x21,0x10,0xFF,0xF3,0xD2}, 45 | {0xCD,0x0C,0x13,0xEC,0x5F,0x97,0x44,0x17,0xC4,0xA7,0x7E,0x3D,0x64,0x5D,0x19,0x73}, 46 | {0x60,0x81,0x4F,0xDC,0x22,0x2A,0x90,0x88,0x46,0xEE,0xB8,0x14,0xDE,0x5E,0x0B,0xDB}, 47 | {0xE0,0x32,0x3A,0x0A,0x49,0x06,0x24,0x5C,0xC2,0xD3,0xAC,0x62,0x91,0x95,0xE4,0x79}, 48 | {0xE7,0xC8,0x37,0x6D,0x8D,0xD5,0x4E,0xA9,0x6C,0x56,0xF4,0xEA,0x65,0x7A,0xAE,0x08}, 49 | {0xBA,0x78,0x25,0x2E,0x1C,0xA6,0xB4,0xC6,0xE8,0xDD,0x74,0x1F,0x4B,0xBD,0x8B,0x8A}, 50 | {0x70,0x3E,0xB5,0x66,0x48,0x03,0xF6,0x0E,0x61,0x35,0x57,0xB9,0x86,0xC1,0x1D,0x9E}, 51 | {0xE1,0xF8,0x98,0x11,0x69,0xD9,0x8E,0x94,0x9B,0x1E,0x87,0xE9,0xCE,0x55,0x28,0xDF}, 52 | {0x8C,0xA1,0x89,0x0D,0xBF,0xE6,0x42,0x68,0x41,0x99,0x2D,0x0F,0xB0,0x54,0xBB,0x16} 53 | }; 54 | 55 | static const BYTE aes_invsbox[16][16] = { 56 | {0x52,0x09,0x6A,0xD5,0x30,0x36,0xA5,0x38,0xBF,0x40,0xA3,0x9E,0x81,0xF3,0xD7,0xFB}, 57 | {0x7C,0xE3,0x39,0x82,0x9B,0x2F,0xFF,0x87,0x34,0x8E,0x43,0x44,0xC4,0xDE,0xE9,0xCB}, 58 | {0x54,0x7B,0x94,0x32,0xA6,0xC2,0x23,0x3D,0xEE,0x4C,0x95,0x0B,0x42,0xFA,0xC3,0x4E}, 59 | {0x08,0x2E,0xA1,0x66,0x28,0xD9,0x24,0xB2,0x76,0x5B,0xA2,0x49,0x6D,0x8B,0xD1,0x25}, 60 | {0x72,0xF8,0xF6,0x64,0x86,0x68,0x98,0x16,0xD4,0xA4,0x5C,0xCC,0x5D,0x65,0xB6,0x92}, 61 | {0x6C,0x70,0x48,0x50,0xFD,0xED,0xB9,0xDA,0x5E,0x15,0x46,0x57,0xA7,0x8D,0x9D,0x84}, 62 | {0x90,0xD8,0xAB,0x00,0x8C,0xBC,0xD3,0x0A,0xF7,0xE4,0x58,0x05,0xB8,0xB3,0x45,0x06}, 63 | {0xD0,0x2C,0x1E,0x8F,0xCA,0x3F,0x0F,0x02,0xC1,0xAF,0xBD,0x03,0x01,0x13,0x8A,0x6B}, 64 | {0x3A,0x91,0x11,0x41,0x4F,0x67,0xDC,0xEA,0x97,0xF2,0xCF,0xCE,0xF0,0xB4,0xE6,0x73}, 65 | {0x96,0xAC,0x74,0x22,0xE7,0xAD,0x35,0x85,0xE2,0xF9,0x37,0xE8,0x1C,0x75,0xDF,0x6E}, 66 | {0x47,0xF1,0x1A,0x71,0x1D,0x29,0xC5,0x89,0x6F,0xB7,0x62,0x0E,0xAA,0x18,0xBE,0x1B}, 67 | {0xFC,0x56,0x3E,0x4B,0xC6,0xD2,0x79,0x20,0x9A,0xDB,0xC0,0xFE,0x78,0xCD,0x5A,0xF4}, 68 | {0x1F,0xDD,0xA8,0x33,0x88,0x07,0xC7,0x31,0xB1,0x12,0x10,0x59,0x27,0x80,0xEC,0x5F}, 69 | {0x60,0x51,0x7F,0xA9,0x19,0xB5,0x4A,0x0D,0x2D,0xE5,0x7A,0x9F,0x93,0xC9,0x9C,0xEF}, 70 | {0xA0,0xE0,0x3B,0x4D,0xAE,0x2A,0xF5,0xB0,0xC8,0xEB,0xBB,0x3C,0x83,0x53,0x99,0x61}, 71 | {0x17,0x2B,0x04,0x7E,0xBA,0x77,0xD6,0x26,0xE1,0x69,0x14,0x63,0x55,0x21,0x0C,0x7D} 72 | }; 73 | 74 | // This table stores pre-calculated values for all possible GF(2^8) calculations.This 75 | // table is only used by the (Inv)MixColumns steps. 76 | // USAGE: The second index (column) is the coefficient of multiplication. Only 7 different 77 | // coefficients are used: 0x01, 0x02, 0x03, 0x09, 0x0b, 0x0d, 0x0e, but multiplication by 78 | // 1 is negligible leaving only 6 coefficients. Each column of the table is devoted to one 79 | // of these coefficients, in the ascending order of value, from values 0x00 to 0xFF. 80 | static const BYTE gf_mul[256][6] = { 81 | {0x00,0x00,0x00,0x00,0x00,0x00},{0x02,0x03,0x09,0x0b,0x0d,0x0e}, 82 | {0x04,0x06,0x12,0x16,0x1a,0x1c},{0x06,0x05,0x1b,0x1d,0x17,0x12}, 83 | {0x08,0x0c,0x24,0x2c,0x34,0x38},{0x0a,0x0f,0x2d,0x27,0x39,0x36}, 84 | {0x0c,0x0a,0x36,0x3a,0x2e,0x24},{0x0e,0x09,0x3f,0x31,0x23,0x2a}, 85 | {0x10,0x18,0x48,0x58,0x68,0x70},{0x12,0x1b,0x41,0x53,0x65,0x7e}, 86 | {0x14,0x1e,0x5a,0x4e,0x72,0x6c},{0x16,0x1d,0x53,0x45,0x7f,0x62}, 87 | {0x18,0x14,0x6c,0x74,0x5c,0x48},{0x1a,0x17,0x65,0x7f,0x51,0x46}, 88 | {0x1c,0x12,0x7e,0x62,0x46,0x54},{0x1e,0x11,0x77,0x69,0x4b,0x5a}, 89 | {0x20,0x30,0x90,0xb0,0xd0,0xe0},{0x22,0x33,0x99,0xbb,0xdd,0xee}, 90 | {0x24,0x36,0x82,0xa6,0xca,0xfc},{0x26,0x35,0x8b,0xad,0xc7,0xf2}, 91 | {0x28,0x3c,0xb4,0x9c,0xe4,0xd8},{0x2a,0x3f,0xbd,0x97,0xe9,0xd6}, 92 | {0x2c,0x3a,0xa6,0x8a,0xfe,0xc4},{0x2e,0x39,0xaf,0x81,0xf3,0xca}, 93 | {0x30,0x28,0xd8,0xe8,0xb8,0x90},{0x32,0x2b,0xd1,0xe3,0xb5,0x9e}, 94 | {0x34,0x2e,0xca,0xfe,0xa2,0x8c},{0x36,0x2d,0xc3,0xf5,0xaf,0x82}, 95 | {0x38,0x24,0xfc,0xc4,0x8c,0xa8},{0x3a,0x27,0xf5,0xcf,0x81,0xa6}, 96 | {0x3c,0x22,0xee,0xd2,0x96,0xb4},{0x3e,0x21,0xe7,0xd9,0x9b,0xba}, 97 | {0x40,0x60,0x3b,0x7b,0xbb,0xdb},{0x42,0x63,0x32,0x70,0xb6,0xd5}, 98 | {0x44,0x66,0x29,0x6d,0xa1,0xc7},{0x46,0x65,0x20,0x66,0xac,0xc9}, 99 | {0x48,0x6c,0x1f,0x57,0x8f,0xe3},{0x4a,0x6f,0x16,0x5c,0x82,0xed}, 100 | {0x4c,0x6a,0x0d,0x41,0x95,0xff},{0x4e,0x69,0x04,0x4a,0x98,0xf1}, 101 | {0x50,0x78,0x73,0x23,0xd3,0xab},{0x52,0x7b,0x7a,0x28,0xde,0xa5}, 102 | {0x54,0x7e,0x61,0x35,0xc9,0xb7},{0x56,0x7d,0x68,0x3e,0xc4,0xb9}, 103 | {0x58,0x74,0x57,0x0f,0xe7,0x93},{0x5a,0x77,0x5e,0x04,0xea,0x9d}, 104 | {0x5c,0x72,0x45,0x19,0xfd,0x8f},{0x5e,0x71,0x4c,0x12,0xf0,0x81}, 105 | {0x60,0x50,0xab,0xcb,0x6b,0x3b},{0x62,0x53,0xa2,0xc0,0x66,0x35}, 106 | {0x64,0x56,0xb9,0xdd,0x71,0x27},{0x66,0x55,0xb0,0xd6,0x7c,0x29}, 107 | {0x68,0x5c,0x8f,0xe7,0x5f,0x03},{0x6a,0x5f,0x86,0xec,0x52,0x0d}, 108 | {0x6c,0x5a,0x9d,0xf1,0x45,0x1f},{0x6e,0x59,0x94,0xfa,0x48,0x11}, 109 | {0x70,0x48,0xe3,0x93,0x03,0x4b},{0x72,0x4b,0xea,0x98,0x0e,0x45}, 110 | {0x74,0x4e,0xf1,0x85,0x19,0x57},{0x76,0x4d,0xf8,0x8e,0x14,0x59}, 111 | {0x78,0x44,0xc7,0xbf,0x37,0x73},{0x7a,0x47,0xce,0xb4,0x3a,0x7d}, 112 | {0x7c,0x42,0xd5,0xa9,0x2d,0x6f},{0x7e,0x41,0xdc,0xa2,0x20,0x61}, 113 | {0x80,0xc0,0x76,0xf6,0x6d,0xad},{0x82,0xc3,0x7f,0xfd,0x60,0xa3}, 114 | {0x84,0xc6,0x64,0xe0,0x77,0xb1},{0x86,0xc5,0x6d,0xeb,0x7a,0xbf}, 115 | {0x88,0xcc,0x52,0xda,0x59,0x95},{0x8a,0xcf,0x5b,0xd1,0x54,0x9b}, 116 | {0x8c,0xca,0x40,0xcc,0x43,0x89},{0x8e,0xc9,0x49,0xc7,0x4e,0x87}, 117 | {0x90,0xd8,0x3e,0xae,0x05,0xdd},{0x92,0xdb,0x37,0xa5,0x08,0xd3}, 118 | {0x94,0xde,0x2c,0xb8,0x1f,0xc1},{0x96,0xdd,0x25,0xb3,0x12,0xcf}, 119 | {0x98,0xd4,0x1a,0x82,0x31,0xe5},{0x9a,0xd7,0x13,0x89,0x3c,0xeb}, 120 | {0x9c,0xd2,0x08,0x94,0x2b,0xf9},{0x9e,0xd1,0x01,0x9f,0x26,0xf7}, 121 | {0xa0,0xf0,0xe6,0x46,0xbd,0x4d},{0xa2,0xf3,0xef,0x4d,0xb0,0x43}, 122 | {0xa4,0xf6,0xf4,0x50,0xa7,0x51},{0xa6,0xf5,0xfd,0x5b,0xaa,0x5f}, 123 | {0xa8,0xfc,0xc2,0x6a,0x89,0x75},{0xaa,0xff,0xcb,0x61,0x84,0x7b}, 124 | {0xac,0xfa,0xd0,0x7c,0x93,0x69},{0xae,0xf9,0xd9,0x77,0x9e,0x67}, 125 | {0xb0,0xe8,0xae,0x1e,0xd5,0x3d},{0xb2,0xeb,0xa7,0x15,0xd8,0x33}, 126 | {0xb4,0xee,0xbc,0x08,0xcf,0x21},{0xb6,0xed,0xb5,0x03,0xc2,0x2f}, 127 | {0xb8,0xe4,0x8a,0x32,0xe1,0x05},{0xba,0xe7,0x83,0x39,0xec,0x0b}, 128 | {0xbc,0xe2,0x98,0x24,0xfb,0x19},{0xbe,0xe1,0x91,0x2f,0xf6,0x17}, 129 | {0xc0,0xa0,0x4d,0x8d,0xd6,0x76},{0xc2,0xa3,0x44,0x86,0xdb,0x78}, 130 | {0xc4,0xa6,0x5f,0x9b,0xcc,0x6a},{0xc6,0xa5,0x56,0x90,0xc1,0x64}, 131 | {0xc8,0xac,0x69,0xa1,0xe2,0x4e},{0xca,0xaf,0x60,0xaa,0xef,0x40}, 132 | {0xcc,0xaa,0x7b,0xb7,0xf8,0x52},{0xce,0xa9,0x72,0xbc,0xf5,0x5c}, 133 | {0xd0,0xb8,0x05,0xd5,0xbe,0x06},{0xd2,0xbb,0x0c,0xde,0xb3,0x08}, 134 | {0xd4,0xbe,0x17,0xc3,0xa4,0x1a},{0xd6,0xbd,0x1e,0xc8,0xa9,0x14}, 135 | {0xd8,0xb4,0x21,0xf9,0x8a,0x3e},{0xda,0xb7,0x28,0xf2,0x87,0x30}, 136 | {0xdc,0xb2,0x33,0xef,0x90,0x22},{0xde,0xb1,0x3a,0xe4,0x9d,0x2c}, 137 | {0xe0,0x90,0xdd,0x3d,0x06,0x96},{0xe2,0x93,0xd4,0x36,0x0b,0x98}, 138 | {0xe4,0x96,0xcf,0x2b,0x1c,0x8a},{0xe6,0x95,0xc6,0x20,0x11,0x84}, 139 | {0xe8,0x9c,0xf9,0x11,0x32,0xae},{0xea,0x9f,0xf0,0x1a,0x3f,0xa0}, 140 | {0xec,0x9a,0xeb,0x07,0x28,0xb2},{0xee,0x99,0xe2,0x0c,0x25,0xbc}, 141 | {0xf0,0x88,0x95,0x65,0x6e,0xe6},{0xf2,0x8b,0x9c,0x6e,0x63,0xe8}, 142 | {0xf4,0x8e,0x87,0x73,0x74,0xfa},{0xf6,0x8d,0x8e,0x78,0x79,0xf4}, 143 | {0xf8,0x84,0xb1,0x49,0x5a,0xde},{0xfa,0x87,0xb8,0x42,0x57,0xd0}, 144 | {0xfc,0x82,0xa3,0x5f,0x40,0xc2},{0xfe,0x81,0xaa,0x54,0x4d,0xcc}, 145 | {0x1b,0x9b,0xec,0xf7,0xda,0x41},{0x19,0x98,0xe5,0xfc,0xd7,0x4f}, 146 | {0x1f,0x9d,0xfe,0xe1,0xc0,0x5d},{0x1d,0x9e,0xf7,0xea,0xcd,0x53}, 147 | {0x13,0x97,0xc8,0xdb,0xee,0x79},{0x11,0x94,0xc1,0xd0,0xe3,0x77}, 148 | {0x17,0x91,0xda,0xcd,0xf4,0x65},{0x15,0x92,0xd3,0xc6,0xf9,0x6b}, 149 | {0x0b,0x83,0xa4,0xaf,0xb2,0x31},{0x09,0x80,0xad,0xa4,0xbf,0x3f}, 150 | {0x0f,0x85,0xb6,0xb9,0xa8,0x2d},{0x0d,0x86,0xbf,0xb2,0xa5,0x23}, 151 | {0x03,0x8f,0x80,0x83,0x86,0x09},{0x01,0x8c,0x89,0x88,0x8b,0x07}, 152 | {0x07,0x89,0x92,0x95,0x9c,0x15},{0x05,0x8a,0x9b,0x9e,0x91,0x1b}, 153 | {0x3b,0xab,0x7c,0x47,0x0a,0xa1},{0x39,0xa8,0x75,0x4c,0x07,0xaf}, 154 | {0x3f,0xad,0x6e,0x51,0x10,0xbd},{0x3d,0xae,0x67,0x5a,0x1d,0xb3}, 155 | {0x33,0xa7,0x58,0x6b,0x3e,0x99},{0x31,0xa4,0x51,0x60,0x33,0x97}, 156 | {0x37,0xa1,0x4a,0x7d,0x24,0x85},{0x35,0xa2,0x43,0x76,0x29,0x8b}, 157 | {0x2b,0xb3,0x34,0x1f,0x62,0xd1},{0x29,0xb0,0x3d,0x14,0x6f,0xdf}, 158 | {0x2f,0xb5,0x26,0x09,0x78,0xcd},{0x2d,0xb6,0x2f,0x02,0x75,0xc3}, 159 | {0x23,0xbf,0x10,0x33,0x56,0xe9},{0x21,0xbc,0x19,0x38,0x5b,0xe7}, 160 | {0x27,0xb9,0x02,0x25,0x4c,0xf5},{0x25,0xba,0x0b,0x2e,0x41,0xfb}, 161 | {0x5b,0xfb,0xd7,0x8c,0x61,0x9a},{0x59,0xf8,0xde,0x87,0x6c,0x94}, 162 | {0x5f,0xfd,0xc5,0x9a,0x7b,0x86},{0x5d,0xfe,0xcc,0x91,0x76,0x88}, 163 | {0x53,0xf7,0xf3,0xa0,0x55,0xa2},{0x51,0xf4,0xfa,0xab,0x58,0xac}, 164 | {0x57,0xf1,0xe1,0xb6,0x4f,0xbe},{0x55,0xf2,0xe8,0xbd,0x42,0xb0}, 165 | {0x4b,0xe3,0x9f,0xd4,0x09,0xea},{0x49,0xe0,0x96,0xdf,0x04,0xe4}, 166 | {0x4f,0xe5,0x8d,0xc2,0x13,0xf6},{0x4d,0xe6,0x84,0xc9,0x1e,0xf8}, 167 | {0x43,0xef,0xbb,0xf8,0x3d,0xd2},{0x41,0xec,0xb2,0xf3,0x30,0xdc}, 168 | {0x47,0xe9,0xa9,0xee,0x27,0xce},{0x45,0xea,0xa0,0xe5,0x2a,0xc0}, 169 | {0x7b,0xcb,0x47,0x3c,0xb1,0x7a},{0x79,0xc8,0x4e,0x37,0xbc,0x74}, 170 | {0x7f,0xcd,0x55,0x2a,0xab,0x66},{0x7d,0xce,0x5c,0x21,0xa6,0x68}, 171 | {0x73,0xc7,0x63,0x10,0x85,0x42},{0x71,0xc4,0x6a,0x1b,0x88,0x4c}, 172 | {0x77,0xc1,0x71,0x06,0x9f,0x5e},{0x75,0xc2,0x78,0x0d,0x92,0x50}, 173 | {0x6b,0xd3,0x0f,0x64,0xd9,0x0a},{0x69,0xd0,0x06,0x6f,0xd4,0x04}, 174 | {0x6f,0xd5,0x1d,0x72,0xc3,0x16},{0x6d,0xd6,0x14,0x79,0xce,0x18}, 175 | {0x63,0xdf,0x2b,0x48,0xed,0x32},{0x61,0xdc,0x22,0x43,0xe0,0x3c}, 176 | {0x67,0xd9,0x39,0x5e,0xf7,0x2e},{0x65,0xda,0x30,0x55,0xfa,0x20}, 177 | {0x9b,0x5b,0x9a,0x01,0xb7,0xec},{0x99,0x58,0x93,0x0a,0xba,0xe2}, 178 | {0x9f,0x5d,0x88,0x17,0xad,0xf0},{0x9d,0x5e,0x81,0x1c,0xa0,0xfe}, 179 | {0x93,0x57,0xbe,0x2d,0x83,0xd4},{0x91,0x54,0xb7,0x26,0x8e,0xda}, 180 | {0x97,0x51,0xac,0x3b,0x99,0xc8},{0x95,0x52,0xa5,0x30,0x94,0xc6}, 181 | {0x8b,0x43,0xd2,0x59,0xdf,0x9c},{0x89,0x40,0xdb,0x52,0xd2,0x92}, 182 | {0x8f,0x45,0xc0,0x4f,0xc5,0x80},{0x8d,0x46,0xc9,0x44,0xc8,0x8e}, 183 | {0x83,0x4f,0xf6,0x75,0xeb,0xa4},{0x81,0x4c,0xff,0x7e,0xe6,0xaa}, 184 | {0x87,0x49,0xe4,0x63,0xf1,0xb8},{0x85,0x4a,0xed,0x68,0xfc,0xb6}, 185 | {0xbb,0x6b,0x0a,0xb1,0x67,0x0c},{0xb9,0x68,0x03,0xba,0x6a,0x02}, 186 | {0xbf,0x6d,0x18,0xa7,0x7d,0x10},{0xbd,0x6e,0x11,0xac,0x70,0x1e}, 187 | {0xb3,0x67,0x2e,0x9d,0x53,0x34},{0xb1,0x64,0x27,0x96,0x5e,0x3a}, 188 | {0xb7,0x61,0x3c,0x8b,0x49,0x28},{0xb5,0x62,0x35,0x80,0x44,0x26}, 189 | {0xab,0x73,0x42,0xe9,0x0f,0x7c},{0xa9,0x70,0x4b,0xe2,0x02,0x72}, 190 | {0xaf,0x75,0x50,0xff,0x15,0x60},{0xad,0x76,0x59,0xf4,0x18,0x6e}, 191 | {0xa3,0x7f,0x66,0xc5,0x3b,0x44},{0xa1,0x7c,0x6f,0xce,0x36,0x4a}, 192 | {0xa7,0x79,0x74,0xd3,0x21,0x58},{0xa5,0x7a,0x7d,0xd8,0x2c,0x56}, 193 | {0xdb,0x3b,0xa1,0x7a,0x0c,0x37},{0xd9,0x38,0xa8,0x71,0x01,0x39}, 194 | {0xdf,0x3d,0xb3,0x6c,0x16,0x2b},{0xdd,0x3e,0xba,0x67,0x1b,0x25}, 195 | {0xd3,0x37,0x85,0x56,0x38,0x0f},{0xd1,0x34,0x8c,0x5d,0x35,0x01}, 196 | {0xd7,0x31,0x97,0x40,0x22,0x13},{0xd5,0x32,0x9e,0x4b,0x2f,0x1d}, 197 | {0xcb,0x23,0xe9,0x22,0x64,0x47},{0xc9,0x20,0xe0,0x29,0x69,0x49}, 198 | {0xcf,0x25,0xfb,0x34,0x7e,0x5b},{0xcd,0x26,0xf2,0x3f,0x73,0x55}, 199 | {0xc3,0x2f,0xcd,0x0e,0x50,0x7f},{0xc1,0x2c,0xc4,0x05,0x5d,0x71}, 200 | {0xc7,0x29,0xdf,0x18,0x4a,0x63},{0xc5,0x2a,0xd6,0x13,0x47,0x6d}, 201 | {0xfb,0x0b,0x31,0xca,0xdc,0xd7},{0xf9,0x08,0x38,0xc1,0xd1,0xd9}, 202 | {0xff,0x0d,0x23,0xdc,0xc6,0xcb},{0xfd,0x0e,0x2a,0xd7,0xcb,0xc5}, 203 | {0xf3,0x07,0x15,0xe6,0xe8,0xef},{0xf1,0x04,0x1c,0xed,0xe5,0xe1}, 204 | {0xf7,0x01,0x07,0xf0,0xf2,0xf3},{0xf5,0x02,0x0e,0xfb,0xff,0xfd}, 205 | {0xeb,0x13,0x79,0x92,0xb4,0xa7},{0xe9,0x10,0x70,0x99,0xb9,0xa9}, 206 | {0xef,0x15,0x6b,0x84,0xae,0xbb},{0xed,0x16,0x62,0x8f,0xa3,0xb5}, 207 | {0xe3,0x1f,0x5d,0xbe,0x80,0x9f},{0xe1,0x1c,0x54,0xb5,0x8d,0x91}, 208 | {0xe7,0x19,0x4f,0xa8,0x9a,0x83},{0xe5,0x1a,0x46,0xa3,0x97,0x8d} 209 | }; 210 | 211 | /*********************** FUNCTION DEFINITIONS ***********************/ 212 | // XORs the in and out buffers, storing the result in out. Length is in bytes. 213 | void xor_buf(const BYTE in[], BYTE out[], size_t len) { 214 | size_t idx; 215 | 216 | for (idx = 0; idx < len; idx++) 217 | out[idx] ^= in[idx]; 218 | } 219 | 220 | /******************* 221 | * AES - CBC 222 | *******************/ 223 | int aes_decrypt_cbc(const BYTE in[], size_t in_len, BYTE out[], const WORD key[], int keysize, const BYTE iv[]) { 224 | BYTE buf_in[AES_BLOCK_SIZE], buf_out[AES_BLOCK_SIZE], iv_buf[AES_BLOCK_SIZE]; 225 | int blocks, idx; 226 | 227 | if (in_len % AES_BLOCK_SIZE != 0) 228 | return(FALSE); 229 | 230 | blocks = in_len / AES_BLOCK_SIZE; 231 | 232 | memcpy(iv_buf, iv, AES_BLOCK_SIZE); 233 | 234 | for (idx = 0; idx < blocks; idx++) { 235 | memcpy(buf_in, &in[idx * AES_BLOCK_SIZE], AES_BLOCK_SIZE); 236 | aes_decrypt(buf_in, buf_out, key, keysize); 237 | xor_buf(iv_buf, buf_out, AES_BLOCK_SIZE); 238 | memcpy(&out[idx * AES_BLOCK_SIZE], buf_out, AES_BLOCK_SIZE); 239 | memcpy(iv_buf, buf_in, AES_BLOCK_SIZE); 240 | } 241 | 242 | return(TRUE); 243 | } 244 | 245 | /******************* 246 | * AES 247 | *******************/ 248 | ///////////////// 249 | // KEY EXPANSION 250 | ///////////////// 251 | 252 | // Substitutes a word using the AES S-Box. 253 | WORD SubWord(WORD word) { 254 | unsigned int result; 255 | 256 | result = (int)aes_sbox[(word >> 4) & 0x0000000F][word & 0x0000000F]; 257 | result += (int)aes_sbox[(word >> 12) & 0x0000000F][(word >> 8) & 0x0000000F] << 8; 258 | result += (int)aes_sbox[(word >> 20) & 0x0000000F][(word >> 16) & 0x0000000F] << 16; 259 | result += (int)aes_sbox[(word >> 28) & 0x0000000F][(word >> 24) & 0x0000000F] << 24; 260 | return(result); 261 | } 262 | 263 | // Performs the action of generating the keys that will be used in every round of 264 | // encryption. "key" is the user-supplied input key, "w" is the output key schedule, 265 | // "keysize" is the length in bits of "key", must be 128, 192, or 256. 266 | void aes_key_setup(const BYTE key[], WORD w[], int keysize) { 267 | int Nb=4,Nr,Nk,idx; 268 | WORD temp,Rcon[]={0x01000000,0x02000000,0x04000000,0x08000000,0x10000000,0x20000000, 269 | 0x40000000,0x80000000,0x1b000000,0x36000000,0x6c000000,0xd8000000, 270 | 0xab000000,0x4d000000,0x9a000000}; 271 | 272 | switch (keysize) { 273 | case 128: Nr = 10; Nk = 4; break; 274 | case 192: Nr = 12; Nk = 6; break; 275 | case 256: Nr = 14; Nk = 8; break; 276 | default: return; 277 | } 278 | 279 | for (idx=0; idx < Nk; ++idx) { 280 | w[idx] = ((key[4 * idx]) << 24) | ((key[4 * idx + 1]) << 16) | 281 | ((key[4 * idx + 2]) << 8) | ((key[4 * idx + 3])); 282 | } 283 | 284 | for (idx = Nk; idx < Nb * (Nr+1); ++idx) { 285 | temp = w[idx - 1]; 286 | if ((idx % Nk) == 0) 287 | temp = SubWord(KE_ROTWORD(temp)) ^ Rcon[(idx-1)/Nk]; 288 | else if (Nk > 6 && (idx % Nk) == 4) 289 | temp = SubWord(temp); 290 | w[idx] = w[idx-Nk] ^ temp; 291 | } 292 | } 293 | 294 | ///////////////// 295 | // ADD ROUND KEY 296 | ///////////////// 297 | 298 | // Performs the AddRoundKey step. Each round has its own pre-generated 16-byte key in the 299 | // form of 4 integers (the "w" array). Each integer is XOR'd by one column of the state. 300 | // Also performs the job of InvAddRoundKey(); since the function is a simple XOR process, 301 | // it is its own inverse. 302 | void AddRoundKey(BYTE state[][4], const WORD w[]) { 303 | BYTE subkey[4]; 304 | 305 | // memcpy(subkey,&w[idx],4); // Not accurate for big endian machines 306 | // Subkey 1 307 | subkey[0] = w[0] >> 24; 308 | subkey[1] = w[0] >> 16; 309 | subkey[2] = w[0] >> 8; 310 | subkey[3] = w[0]; 311 | state[0][0] ^= subkey[0]; 312 | state[1][0] ^= subkey[1]; 313 | state[2][0] ^= subkey[2]; 314 | state[3][0] ^= subkey[3]; 315 | // Subkey 2 316 | subkey[0] = w[1] >> 24; 317 | subkey[1] = w[1] >> 16; 318 | subkey[2] = w[1] >> 8; 319 | subkey[3] = w[1]; 320 | state[0][1] ^= subkey[0]; 321 | state[1][1] ^= subkey[1]; 322 | state[2][1] ^= subkey[2]; 323 | state[3][1] ^= subkey[3]; 324 | // Subkey 3 325 | subkey[0] = w[2] >> 24; 326 | subkey[1] = w[2] >> 16; 327 | subkey[2] = w[2] >> 8; 328 | subkey[3] = w[2]; 329 | state[0][2] ^= subkey[0]; 330 | state[1][2] ^= subkey[1]; 331 | state[2][2] ^= subkey[2]; 332 | state[3][2] ^= subkey[3]; 333 | // Subkey 4 334 | subkey[0] = w[3] >> 24; 335 | subkey[1] = w[3] >> 16; 336 | subkey[2] = w[3] >> 8; 337 | subkey[3] = w[3]; 338 | state[0][3] ^= subkey[0]; 339 | state[1][3] ^= subkey[1]; 340 | state[2][3] ^= subkey[2]; 341 | state[3][3] ^= subkey[3]; 342 | } 343 | 344 | ///////////////// 345 | // InvSubBytes 346 | ///////////////// 347 | void InvSubBytes(BYTE state[][4]) { 348 | state[0][0] = aes_invsbox[state[0][0] >> 4][state[0][0] & 0x0F]; 349 | state[0][1] = aes_invsbox[state[0][1] >> 4][state[0][1] & 0x0F]; 350 | state[0][2] = aes_invsbox[state[0][2] >> 4][state[0][2] & 0x0F]; 351 | state[0][3] = aes_invsbox[state[0][3] >> 4][state[0][3] & 0x0F]; 352 | state[1][0] = aes_invsbox[state[1][0] >> 4][state[1][0] & 0x0F]; 353 | state[1][1] = aes_invsbox[state[1][1] >> 4][state[1][1] & 0x0F]; 354 | state[1][2] = aes_invsbox[state[1][2] >> 4][state[1][2] & 0x0F]; 355 | state[1][3] = aes_invsbox[state[1][3] >> 4][state[1][3] & 0x0F]; 356 | state[2][0] = aes_invsbox[state[2][0] >> 4][state[2][0] & 0x0F]; 357 | state[2][1] = aes_invsbox[state[2][1] >> 4][state[2][1] & 0x0F]; 358 | state[2][2] = aes_invsbox[state[2][2] >> 4][state[2][2] & 0x0F]; 359 | state[2][3] = aes_invsbox[state[2][3] >> 4][state[2][3] & 0x0F]; 360 | state[3][0] = aes_invsbox[state[3][0] >> 4][state[3][0] & 0x0F]; 361 | state[3][1] = aes_invsbox[state[3][1] >> 4][state[3][1] & 0x0F]; 362 | state[3][2] = aes_invsbox[state[3][2] >> 4][state[3][2] & 0x0F]; 363 | state[3][3] = aes_invsbox[state[3][3] >> 4][state[3][3] & 0x0F]; 364 | } 365 | 366 | ///////////////// 367 | // InvShiftRows 368 | ///////////////// 369 | // All rows are shifted cylindrically to the right. 370 | void InvShiftRows(BYTE state[][4]) { 371 | int t; 372 | 373 | // Shift right by 1 374 | t = state[1][3]; 375 | state[1][3] = state[1][2]; 376 | state[1][2] = state[1][1]; 377 | state[1][1] = state[1][0]; 378 | state[1][0] = t; 379 | // Shift right by 2 380 | t = state[2][3]; 381 | state[2][3] = state[2][1]; 382 | state[2][1] = t; 383 | t = state[2][2]; 384 | state[2][2] = state[2][0]; 385 | state[2][0] = t; 386 | // Shift right by 3 387 | t = state[3][3]; 388 | state[3][3] = state[3][0]; 389 | state[3][0] = state[3][1]; 390 | state[3][1] = state[3][2]; 391 | state[3][2] = t; 392 | } 393 | 394 | ///////////////// 395 | // InvColumns 396 | ///////////////// 397 | void InvMixColumns(BYTE state[][4]) { 398 | BYTE col[4]; 399 | 400 | // Column 1 401 | col[0] = state[0][0]; 402 | col[1] = state[1][0]; 403 | col[2] = state[2][0]; 404 | col[3] = state[3][0]; 405 | state[0][0] = gf_mul[col[0]][5]; 406 | state[0][0] ^= gf_mul[col[1]][3]; 407 | state[0][0] ^= gf_mul[col[2]][4]; 408 | state[0][0] ^= gf_mul[col[3]][2]; 409 | state[1][0] = gf_mul[col[0]][2]; 410 | state[1][0] ^= gf_mul[col[1]][5]; 411 | state[1][0] ^= gf_mul[col[2]][3]; 412 | state[1][0] ^= gf_mul[col[3]][4]; 413 | state[2][0] = gf_mul[col[0]][4]; 414 | state[2][0] ^= gf_mul[col[1]][2]; 415 | state[2][0] ^= gf_mul[col[2]][5]; 416 | state[2][0] ^= gf_mul[col[3]][3]; 417 | state[3][0] = gf_mul[col[0]][3]; 418 | state[3][0] ^= gf_mul[col[1]][4]; 419 | state[3][0] ^= gf_mul[col[2]][2]; 420 | state[3][0] ^= gf_mul[col[3]][5]; 421 | // Column 2 422 | col[0] = state[0][1]; 423 | col[1] = state[1][1]; 424 | col[2] = state[2][1]; 425 | col[3] = state[3][1]; 426 | state[0][1] = gf_mul[col[0]][5]; 427 | state[0][1] ^= gf_mul[col[1]][3]; 428 | state[0][1] ^= gf_mul[col[2]][4]; 429 | state[0][1] ^= gf_mul[col[3]][2]; 430 | state[1][1] = gf_mul[col[0]][2]; 431 | state[1][1] ^= gf_mul[col[1]][5]; 432 | state[1][1] ^= gf_mul[col[2]][3]; 433 | state[1][1] ^= gf_mul[col[3]][4]; 434 | state[2][1] = gf_mul[col[0]][4]; 435 | state[2][1] ^= gf_mul[col[1]][2]; 436 | state[2][1] ^= gf_mul[col[2]][5]; 437 | state[2][1] ^= gf_mul[col[3]][3]; 438 | state[3][1] = gf_mul[col[0]][3]; 439 | state[3][1] ^= gf_mul[col[1]][4]; 440 | state[3][1] ^= gf_mul[col[2]][2]; 441 | state[3][1] ^= gf_mul[col[3]][5]; 442 | // Column 3 443 | col[0] = state[0][2]; 444 | col[1] = state[1][2]; 445 | col[2] = state[2][2]; 446 | col[3] = state[3][2]; 447 | state[0][2] = gf_mul[col[0]][5]; 448 | state[0][2] ^= gf_mul[col[1]][3]; 449 | state[0][2] ^= gf_mul[col[2]][4]; 450 | state[0][2] ^= gf_mul[col[3]][2]; 451 | state[1][2] = gf_mul[col[0]][2]; 452 | state[1][2] ^= gf_mul[col[1]][5]; 453 | state[1][2] ^= gf_mul[col[2]][3]; 454 | state[1][2] ^= gf_mul[col[3]][4]; 455 | state[2][2] = gf_mul[col[0]][4]; 456 | state[2][2] ^= gf_mul[col[1]][2]; 457 | state[2][2] ^= gf_mul[col[2]][5]; 458 | state[2][2] ^= gf_mul[col[3]][3]; 459 | state[3][2] = gf_mul[col[0]][3]; 460 | state[3][2] ^= gf_mul[col[1]][4]; 461 | state[3][2] ^= gf_mul[col[2]][2]; 462 | state[3][2] ^= gf_mul[col[3]][5]; 463 | // Column 4 464 | col[0] = state[0][3]; 465 | col[1] = state[1][3]; 466 | col[2] = state[2][3]; 467 | col[3] = state[3][3]; 468 | state[0][3] = gf_mul[col[0]][5]; 469 | state[0][3] ^= gf_mul[col[1]][3]; 470 | state[0][3] ^= gf_mul[col[2]][4]; 471 | state[0][3] ^= gf_mul[col[3]][2]; 472 | state[1][3] = gf_mul[col[0]][2]; 473 | state[1][3] ^= gf_mul[col[1]][5]; 474 | state[1][3] ^= gf_mul[col[2]][3]; 475 | state[1][3] ^= gf_mul[col[3]][4]; 476 | state[2][3] = gf_mul[col[0]][4]; 477 | state[2][3] ^= gf_mul[col[1]][2]; 478 | state[2][3] ^= gf_mul[col[2]][5]; 479 | state[2][3] ^= gf_mul[col[3]][3]; 480 | state[3][3] = gf_mul[col[0]][3]; 481 | state[3][3] ^= gf_mul[col[1]][4]; 482 | state[3][3] ^= gf_mul[col[2]][2]; 483 | state[3][3] ^= gf_mul[col[3]][5]; 484 | } 485 | 486 | ///////////////// 487 | // Decrypt 488 | ///////////////// 489 | void aes_decrypt(const BYTE in[], BYTE out[], const WORD key[], int keysize) { 490 | BYTE state[4][4]; 491 | 492 | // Copy the input to the state. 493 | state[0][0] = in[0]; 494 | state[1][0] = in[1]; 495 | state[2][0] = in[2]; 496 | state[3][0] = in[3]; 497 | state[0][1] = in[4]; 498 | state[1][1] = in[5]; 499 | state[2][1] = in[6]; 500 | state[3][1] = in[7]; 501 | state[0][2] = in[8]; 502 | state[1][2] = in[9]; 503 | state[2][2] = in[10]; 504 | state[3][2] = in[11]; 505 | state[0][3] = in[12]; 506 | state[1][3] = in[13]; 507 | state[2][3] = in[14]; 508 | state[3][3] = in[15]; 509 | 510 | // Perform the necessary number of rounds. The round key is added first. 511 | // The last round does not perform the MixColumns step. 512 | if (keysize > 128) { 513 | if (keysize > 192) { 514 | AddRoundKey(state,&key[56]); 515 | InvShiftRows(state);InvSubBytes(state);AddRoundKey(state,&key[52]);InvMixColumns(state); 516 | InvShiftRows(state);InvSubBytes(state);AddRoundKey(state,&key[48]);InvMixColumns(state); 517 | } 518 | else { 519 | AddRoundKey(state,&key[48]); 520 | } 521 | InvShiftRows(state);InvSubBytes(state);AddRoundKey(state,&key[44]);InvMixColumns(state); 522 | InvShiftRows(state);InvSubBytes(state);AddRoundKey(state,&key[40]);InvMixColumns(state); 523 | } 524 | else { 525 | AddRoundKey(state,&key[40]); 526 | } 527 | InvShiftRows(state);InvSubBytes(state);AddRoundKey(state,&key[36]);InvMixColumns(state); 528 | InvShiftRows(state);InvSubBytes(state);AddRoundKey(state,&key[32]);InvMixColumns(state); 529 | InvShiftRows(state);InvSubBytes(state);AddRoundKey(state,&key[28]);InvMixColumns(state); 530 | InvShiftRows(state);InvSubBytes(state);AddRoundKey(state,&key[24]);InvMixColumns(state); 531 | InvShiftRows(state);InvSubBytes(state);AddRoundKey(state,&key[20]);InvMixColumns(state); 532 | InvShiftRows(state);InvSubBytes(state);AddRoundKey(state,&key[16]);InvMixColumns(state); 533 | InvShiftRows(state);InvSubBytes(state);AddRoundKey(state,&key[12]);InvMixColumns(state); 534 | InvShiftRows(state);InvSubBytes(state);AddRoundKey(state,&key[8]);InvMixColumns(state); 535 | InvShiftRows(state);InvSubBytes(state);AddRoundKey(state,&key[4]);InvMixColumns(state); 536 | InvShiftRows(state);InvSubBytes(state);AddRoundKey(state,&key[0]); 537 | 538 | // Copy the state to the output array. 539 | out[0] = state[0][0]; 540 | out[1] = state[1][0]; 541 | out[2] = state[2][0]; 542 | out[3] = state[3][0]; 543 | out[4] = state[0][1]; 544 | out[5] = state[1][1]; 545 | out[6] = state[2][1]; 546 | out[7] = state[3][1]; 547 | out[8] = state[0][2]; 548 | out[9] = state[1][2]; 549 | out[10] = state[2][2]; 550 | out[11] = state[3][2]; 551 | out[12] = state[0][3]; 552 | out[13] = state[1][3]; 553 | out[14] = state[2][3]; 554 | out[15] = state[3][3]; 555 | } --------------------------------------------------------------------------------