All Projects → Catherine22 → SecuritySample

Catherine22 / SecuritySample

Licence: Apache-2.0 license
(Android) Hide encrypted secret API keys in C/C++ code, retrieve and decrypt them via JNI. Google SafetyNet APIs example.

Programming Languages

java
68154 projects - #9 most used programming language
C++
36643 projects - #6 most used programming language

Projects that are alternatives of or similar to SecuritySample

Go Jose
An implementation of JOSE standards (JWE, JWS, JWT) in Go
Stars: ✭ 1,849 (+3673.47%)
Mutual labels:  jws, json-web-signature
jwx
JSON/JWK/JWS/JWT/Base64 library in SPARK
Stars: ✭ 15 (-69.39%)
Mutual labels:  jws, json-web-signature
devicecheck-appattest
Server-side library to validate the authenticity of Apple App Attest artifacts, written in Kotlin.
Stars: ✭ 45 (-8.16%)
Mutual labels:  attestation
jwt-core
[READ-ONLY] Core component of the JWT Framework
Stars: ✭ 46 (-6.12%)
Mutual labels:  jws
rhonabwy
Javascript Object Signing and Encryption (JOSE) library - JWK, JWKS, JWS, JWE and JWT
Stars: ✭ 33 (-32.65%)
Mutual labels:  jws
tls certificate generation
Use temporary Amazon EC2 / Digital Ocean cloud machines to get / renew letsencrypt certificates
Stars: ✭ 28 (-42.86%)
Mutual labels:  certificates
confidential-computing-zoo
Confidential Computing Zoo provides confidential computing solutions based on Intel SGX, TDX, HEXL, etc. technologies.
Stars: ✭ 177 (+261.22%)
Mutual labels:  attestation
mbedcrypto
a portable, small, easy to use and fast c++14 library for cryptography.
Stars: ✭ 38 (-22.45%)
Mutual labels:  rsa-cryptography
Android-T3
Android 核心知识点整理归纳!!!
Stars: ✭ 315 (+542.86%)
Mutual labels:  ndk
BlueRSA
RSA public/private key encryption, private key signing and public key verification in Swift using the Swift Package Manager. Works on iOS, macOS, and Linux (work in progress).
Stars: ✭ 123 (+151.02%)
Mutual labels:  rsa-cryptography
PersonDemo
🔥 一些个人学习中备份的技术方案
Stars: ✭ 16 (-67.35%)
Mutual labels:  ndk
bowser
a smart, friendly, secure, and auditable ssh daemon
Stars: ✭ 44 (-10.2%)
Mutual labels:  certificates
AttestationSamples
A small subset of the submitted sample data from https://github.com/GrapheneOS/Auditor. It has a sample attestation certificate chain per device model (ro.product.model) along with a subset of the system properties from the sample as supplementary information.
Stars: ✭ 25 (-48.98%)
Mutual labels:  attestation
secret config
Centralized Configuration and Secrets Management for Ruby and Rails applications.
Stars: ✭ 15 (-69.39%)
Mutual labels:  secret-keys
Android-Code-Demos
📦 Android learning code demos.
Stars: ✭ 41 (-16.33%)
Mutual labels:  ndk
AzureWebAppSSLManager
Acquires and manages free SSL certificates for Azure Web App and Azure Functions applications.
Stars: ✭ 70 (+42.86%)
Mutual labels:  certificates
sscg
Simple Signed Certificate Generator
Stars: ✭ 57 (+16.33%)
Mutual labels:  certificates
kms-issuer
KMS issuer is a cert-manager Certificate Request controller that uses AWS KMS to sign the certificate request.
Stars: ✭ 43 (-12.24%)
Mutual labels:  certificates
cassler
🕷️ 🕷️ 🕷️ Validate SSL Certificates around web
Stars: ✭ 55 (+12.24%)
Mutual labels:  certificates
httpsbook
《深入浅出HTTPS:从原理到实战》代码示例、勘误、反馈、讨论
Stars: ✭ 77 (+57.14%)
Mutual labels:  certificates

SecuritySample

Hiding encrypted secret API keys in C/C++ code and decrypting them via JNI.

Native code is harder to decompile than Java code. That's what you write secret keys in C/C++ code. To be safer, you can encrypt those secret keys before you fill in them. So you have to decrypt them to use.

Using SafetyNet Attestation APIs.

SafetyNet is a nifty solution in the following scenarios:

  1. I'm not sure if the app which is connecting to my server is that app I published.
  2. Can I trust this Android API?
  3. Is this a real, compatible device?
  4. Whether my application is running on a rooted device or not.

SafetyNet APIs are used to evaluate if the environment where your app runs is safe and compatible with the Android API or not. Verify the integrity, compatibility and signature of your app by calling Attestation APIs. Let your server decide to continue or to stop connecting to that untrusted device immediately.

Features

1. Get encrypted data from native code through NDK

  • Hiding Secret keys in C/C++ code.
  • Using RSAHelper to encrypt your secret keys (For example, authorization key, public key, iv parameters of DES algorithm or something). Paste those encrypted strings to this project. Then, fill in the parameters generated by RSAHelper in this project for decryption to decrypt the messages.

2. Evaluate the security and compatibility of the Android environments in which your apps run

  • Call SafetyNet Attestation APIs.

Instruction of JNI, encryption and decryption

In the begining, you might need to create a keystore.properties file to keep some information you need.

storeFile=/Users/workspace/Keystores/xxx.jks
storePassword=xxxx
keyAlias=xxxx
keyPassword=xxxx

Step1. Generate a pair of RSA keys and encrypt your messages.

Run RSAHelper to get encrypted messages, using RSA modulus and exponent for decryption.

Step2. Fill in MODULUS and EXPONENT

Hide RSA parameters in Config.cpp

JNIEXPORT jobjectArray JNICALL
Java_com_catherine_securitysample_JNIHelper_getKeyParams(JNIEnv *env, jobject instance) {
    jobjectArray valueArray = (jobjectArray) env->NewObjectArray(2, env->FindClass("java/lang/String"), 0);
    const char *hash[2];
    //MODULUS
    hash[0] = "Fill in the modulus created by RSAHelper";
    //EXPONENT
    hash[1] = "Fill in the exponent created by RSAHelper";
    for (int i = 0; i < 2; i++) {
        jstring value = env->NewStringUTF(hash[i]);
        env->SetObjectArrayElement(valueArray, i, value);
    }
    return valueArray;
}

Step3. Add the decryption method to your project

In JNIHelper,

/**
 * Decrypt messages by RSA algorithm<br>
 *
 * @param message
 * @return Original message
 * @throws NoSuchAlgorithmException
 * @throws NoSuchPaddingException
 * @throws InvalidKeyException
 * @throws IllegalBlockSizeException
 * @throws BadPaddingException
 * @throws UnsupportedEncodingException
 * @throws InvalidAlgorithmParameterException
 * @throws InvalidKeySpecException
 * @throws ClassNotFoundException
 */
public String decryptRSA(String message) throws NoSuchAlgorithmException, NoSuchPaddingException,
        InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException,
        InvalidAlgorithmParameterException, ClassNotFoundException, InvalidKeySpecException {
    Cipher c2 = Cipher.getInstance(Algorithm.rules.get("RSA")); // 创建一个Cipher对象,注意这里用的算法需要和Key的算法匹配

    BigInteger m = new BigInteger(Base64.decode(getKeyParams()[0].getBytes(), Base64.DEFAULT));
    BigInteger e = new BigInteger(Base64.decode(getKeyParams()[1].getBytes(), Base64.DEFAULT));
    c2.init(Cipher.DECRYPT_MODE, convertStringToPublicKey(m, e)); // 设置Cipher为解密工作模式,需要把Key传进去
    byte[] decryptedData = c2.doFinal(Base64.decode(message.getBytes(), Base64.DEFAULT));
    return new String(decryptedData, Algorithm.CHARSET);
}

/**
 * You can component a publicKey by a specific pair of values - modulus and
 * exponent.
 *
 * @param modulus  When you generate a new RSA KeyPair, you'd get a PrivateKey, a
 *                 modulus and an exponent.
 * @param exponent When you generate a new RSA KeyPair, you'd get a PrivateKey, a
 *                 modulus and an exponent.
 * @throws ClassNotFoundException
 * @throws NoSuchAlgorithmException
 * @throws InvalidKeySpecException
 */
private Key convertStringToPublicKey(BigInteger modulus, BigInteger exponent)
        throws ClassNotFoundException, NoSuchAlgorithmException, InvalidKeySpecException {
    byte[] modulusByteArry = modulus.toByteArray();
    byte[] exponentByteArry = exponent.toByteArray();

    RSAPublicKeySpec rsaPublicKeySpec = new RSAPublicKeySpec(new BigInteger(modulusByteArry),
            new BigInteger(exponentByteArry));
    KeyFactory kFactory = KeyFactory.getInstance(Algorithm.KEYPAIR_ALGORITHM);
    PublicKey publicKey = kFactory.generatePublic(rsaPublicKeySpec);
    return publicKey;
}

Step4. Create C/C++ files

  • There are two ways to use JNI -- CmakeLists.txt and Android.mk, I used Android.mk here.
  • Create jni folder in main/ .Then add Android.mk, Application.mk and C/C++ files(Config.cpp).

JNI 1

  • In build.gradle:
externalNativeBuild {
    ndkBuild {
        path 'src/main/jni/Android.mk'
    }
}
static {
    //relate to LOCAL_MODULE in Android.mk
    System.loadLibrary("keys");
}
/**
 * A native method that is implemented by the 'native-lib' native library,
 * which is packaged with this application.
 */
public native String[] getAuthChain(String key);

/**
 * A native method that is implemented by the 'native-lib' native library,
 * which is packaged with this application.
 */
public native String[] getKeyParams();

Step5. Run your app

    private final static String TAG = "MainActivity";

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        StringBuilder sb = new StringBuilder();
    try {
        // Example of a call to a native method
        TextView tv = (TextView) findViewById(R.id.sample_text);

        String[] authChain = getAuthChain("LOGIN");
        sb.append("Decrypted secret keys\n[ ");
        for (int i = 0; i < authChain.length; i++) {
            sb.append(decryptRSA(authChain[i]));
            sb.append(" ");
        }
        sb.append("]\n");

        String[] authChain2 = getAuthChain("OTHER");
        sb.append("secret keys\n[ ");
        for (int i = 0; i < authChain.length; i++) {
            sb.append(authChain2[i]);
            sb.append(" ");
        }
        sb.append("]");
        Log.d(TAG, sb.toString());
        tv.setText(sb.toString());
    } catch (Exception e) {
        e.printStackTrace();
    }
}

Instruction of SafetyNet Attestation APIs

In the begining, you might need to create a keystore.properties file to keep some information you need.

storeFile=/Users//Keystores/xxx.jks
storePassword=xxxx
keyAlias=xxxx
keyPassword=xxxx

Things you must know before you start developing.

  1. Use SafetyNetApi the deprecated class or you'd probably get 403 error by calling SafetyNet.getClient(context)
  2. JWS (JSON Web Token) contains header, payload and signature, your environment information is refer to the payload.
  3. There are two APIs you might need - SafetyNet API and Android device verification API. You get your device and app information with SafetyNet API, and check whether the information is truthful with another. Then let your server decide the next step (like shutting down the app or something).
  4. Attestation should not run on your UI thread, you can use HandlerThread to deal with this situation.

JWS Header: A string representing a JSON object that describes the digital signature or MAC operation applied to create the JWS Signature value. JWS Payload: The bytes to be secured -- aka, the message. The payload can contain an arbitrary sequence of bytes. JWS Signature: A byte array containing the cryptographic material that secures the JWS Header and the JWS Payload. For more information, see https://tools.ietf.org/html/rfc7515

Step1. Generate an API key from google developers console (optional)

  • You can skip this step if you don't verify your attestation response from google APIs (I feel like this step is kind of like https validation. It probabily means man-in-the-middle attacks are allowed if you do not check the response.). Of course you can also validate the SSL certificate chain by yourself. Google highly recommends you to check your JWS statement.

  • What "Android Device Verification API" dose is only checking JWS certificates and signatures. Its response (JSON payload) has nothing to do with the Android environments in which your app run.

  • Get your API key here: https://console.developers.google.com/, and don't forget to add and enable "Android Device Verification API".

  • Make sure the API key you post to "Android Device Verification API" is unrestricted.

  • There is a daily quota restriction of connecting "Android Device Verification API".

  • In gradle.porpeties, add your google API key

safetynet_api_key = XXXXXXXXX
  • In build.gradle
android {
  defaultConfig {
          buildConfigField("String", "API_KEY", "\"${safetynet_api_key}\"")
      }
}
  • DO NOT add any safetyNet meta-data in your manifest
<!--<meta-data-->
    <!--android:name="com.google.android.safetynet.ATTEST_API_KEY"-->
    <!--android:value="${safetynet_api_key}" />-->

Step2. Build GoogleApiClient and call SafetyNet APIs

In MyApplication,

public class MyApplication extends Application {
    public HandlerThread safetyNetLooper;
    public static MyApplication INSTANCE;

    @Override
    public void onCreate() {
        INSTANCE = this;
        safetyNetLooper = new HandlerThread("SafetyNet task");
        safetyNetLooper.start();
        super.onCreate();
    }
}

In manifest,

<application
    android:name=".MyApplication">
</application>
SafetyNetHelper safetyNetHelper = new SafetyNetHelper(BuildConfig.API_KEY);
Handler handler = new Handler(MyApplication.INSTANCE.safetyNetLooper.getLooper());
GoogleApiClient googleApiClient = new GoogleApiClient.Builder(contex)
        .addApi(SafetyNet.API)
        .addConnectionCallbacks(googleApiConnectionCallbacks)
        .addOnConnectionFailedListener(googleApiConnectionFailedListener)
        .setHandler(handler) //Run on a new thread
        .build();
//Don't forget to connect!
googleApiClient.connect();
byte[] requestNonce = generateOneTimeRequestNonce();
SafetyNet.SafetyNetApi.attest(googleApiClient, requestNonce)
        .setResultCallback(new ResultCallback<SafetyNetApi.AttestationResult>() {

            @Override
            public void onResult(@NonNull SafetyNetApi.AttestationResult attestationResult) {
                Status status = attestationResult.getStatus();
                boolean isSuccess = status.isSuccess();
                if (!isSuccess)
                    callback.onFail(ErrorMessage.SAFETY_NET_API_NOT_WORK, ErrorMessage.SAFETY_NET_API_NOT_WORK.name());
                else {
                    try {
                        final String jwsResult = attestationResult.getJwsResult();
                        final JwsHelper jwsHelper = new JwsHelper(jwsResult);
                        final AttestationResult response = new AttestationResult(jwsHelper.getDecodedPayload());
                        if (!verifyJWSResponse) {
                            callback.onResponse(response.getFormattedString());

                            //release SafetyNet HandlerThread
                            MyApplication.INSTANCE.safetyNetLooper.quit();
                        } else {
                            AndroidDeviceVerifier androidDeviceVerifier = new AndroidDeviceVerifier(ctx, jwsResult);
                            androidDeviceVerifier.verify(new AttestationTaskCallback() {
                                @Override
                                public void error(String errorMsg) {
                                    callback.onFail(ErrorMessage.FAILED_TO_CALL_GOOGLE_API_SERVICES, errorMsg);

                                    //release SafetyNet HandlerThread
                                    MyApplication.INSTANCE.safetyNetLooper.quit();
                                }

                                @Override
                                public void success(boolean isValidSignature) {
                                    if (isValidSignature)
                                        callback.onResponse("isValidSignature true\n\n" + response.getFormattedString());
                                    else
                                        callback.onFail(ErrorMessage.ERROR_VALID_SIGNATURE, ErrorMessage.ERROR_VALID_SIGNATURE.name());

                                    //release SafetyNet HandlerThread
                                    MyApplication.INSTANCE.safetyNetLooper.quit();
                                }
                            });
                        }
                    } catch (JSONException e) {
                        callback.onFail(ErrorMessage.EXCEPTION, e.getMessage());

                        //release SafetyNet HandlerThread
                        MyApplication.INSTANCE.safetyNetLooper.quit();
                    }
                }
            }
        });

Step3. Call Attestation API to retrieve JWS messages

The JWS payloads I got by running this app on the real device and the nox monitor are a little different.

  • On my mobile phone, ctsProfileMatch and basicIntegrity were both true.
{
  "nonce":"pUkGirEXYOQefux33VWeSEmR0kBkLNGQaiQiZvE3VAc=",
  "timestampMs":1498814112718,
  "apkPackageName":"com.catherine.securitysample",
  "apkDigestSha256":"FPgrs1x05EaZiJkfKaitzEXTazg+GDDqYtbR5XyJiJE=",
  "ctsProfileMatch":true,
  "extension":"CbRP9k08+pZE",
  "apkCertificateDigestSha256":["9mLFS3eHWOBcHlA4MmODmfGvzgkbg2YSQ2z/ww9lCfw="],
  "basicIntegrity":true
}
  • On a rooted one, ctsProfileMatch and basicIntegrity were both false.
{
  "nonce":"FWypInssEmM+YBl61JCVPFx+bC5naGuIPQhkP3ait68=",
  "timestampMs":1502958413970,
  "apkPackageName":"",
  "apkDigestSha256":"",
  "ctsProfileMatch":false,
  "extension":"CdVwxgDa4bqk",
  "apkCertificateDigestSha256":"",
  "basicIntegrity":false
}

Step4. Verify your JWS response (optional)

  • First you must finish step1.
  • You can skip this step if you don't verify your attestation response from google APIs (I feel like this step is kind of like https validation. It probabily means man-in-the-middle attacks are allowed if you do not check the response.). Of course you can also validate the SSL certificate chain by yourself. Google highly recommends you to check your JWS statement.
  • What "Android Device Verification API" dose is only checking JWS certificates and signatures. Its response (JSON payload) has nothing to do with the Android environments in which your app run.
  • I have this app call google Android Device Verification API until daily API queries exceed the quota limit. Then, instead of google server, the JWS response will be verified by devices. Here is a sample AttestationAsyncTask.

Follow these steps to verify the origin of the JWS message:

  1. Extract the SSL certificate chain from the JWS message.
  2. Validate the SSL certificate chain and use SSL hostname matching to verify that the leaf certificate was issued to the hostname attest.android.com.
  3. Use the certificate to verify the signature of the JWS message.

Step5. Back to your application

  • Post the JWS payload to your server to check the payload and return commands to your app.

If you want to read more about google security services for Android, you can watch Google Security Services for Android: Mobile Protections at Google Scale, the YouTube video. Or you could see README_cn, they are almost the same.

Your workflow would be one of them:

  1. (Security risk) Call Attestation APIs → Get a JWS response → Send JWS to your server → ?? - it depends on your server.
  2. (Recommendation) Call Attestation APIs → Get a JWS response → Check the JWS response (step 4) → Send valid JWS to your server → ?? - it depends on your server.

Warnings

As you add new secret keys, you must refill modulus, exponent and the other encrypted keys, because you'll get different RSA KeyPair (private key and public key) for every execution.

Reference

License

Copyright 2017 Catherine Chen (https://github.com/Catherine22)

Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy of
the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License.
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].