使用Android NDK编译Cryptoauthlib库
1. 前言
目前在嵌入式Android平台中,对于安全性的需求越来越多,很多客户选择Microchip的Secure Element方案,比如ATSHA204A,ATECC608或TA10x系列芯片,都要用到Microchip的安全芯片库Cryptoauthlib。不可避免地面临到如何把Cryptoauthlib编译到Android平台中。本文简单地介绍一下使用步骤。
2. 前期准备
本文是在Windows 11的WSL2环境下,安装了Ubuntu 24.04。
将Android NDK安装在Ubuntu 24.04系统中。
在Windows 11中安装最新的Android Studio。
2.1 下载安装Android NDK(Ubuntu 24.04中)
Android NDK是专门用来编译C/C++语言的。为了防止兼容性问题,我们在Linux下把CAL库编译so。
在https://developer.android.google.cn/ndk/downloads?hl=zh-cn下载Android-NDK,平台选择Linux 64 位 (x86)版本的,这里下载的是android-ndk-r27c-linux.zip
如果想下载旧版本的NDK,可以在这里下载:https://github.com/android/ndk/wiki/Unsupported-Downloads
2.2 添加到Ubuntu系统PATH
将下载的android-ndk-r27c-linux.zip解压到用户目录/home/user_name中。
$ nano ~/.bashrc
# 在最下面添加下面的代码
export ANDROID_NDK="/home/user_name/android-ndk-r27c"
export PATH="$ANDROID_NDK;$PATH"
# Ctrl + S保存配置; Ctrl + X退出
# 更新系统PATH
$ source ~/.bashrc
# 输出ANDROID_NDK路径验证一下
$ echo $ANDROID_NDK
/home/user_name/android-ndk-r27c
2.3 下载和安装Android Studio (Windows 11)
在Google官网下载Android Studio。按步骤安装好Studio。
安装好后,打开Studio,在SDK Manager中安装需要的SDK Platform,这里主要是Android 11。
安装Android SDK Build-Tools,Android NDK和CMake。Android NDK最好和Ubuntu中使用相同的版本。
在用户的环境变量中添加:
ANDROID_NDK = C:\Users\MyPcName\AppData\Local\Android\Sdk\ndk\27.0.12077973
PATH = %PATH%; %ANDROID_NDK%;C:\Users\MyPcName\AppData\Local\Android\Sdk\platform-tools
这样在Windows PowerShell命令行中就可以直接调用adb命令了。
3. 下载Cryptoauthlib和编译成so
3.1 下载Cryptoauthlib最新版本
Microchip会不定期更新Cryptoauthlib版本,我们可以在github中下载最新的版本:
$ git clone https://github.com/MicrochipTech/cryptoauthlib
由于国内网络访问github比较慢,可以用下面的备用库
$ git clone https://gitee.com/flyerink/cryptoauthlib
3.2 编译出.so库
我们使用的目录板是Raspberry PI 4B开发板,这是基于ARM aarch64架构的SoC,运行Android 11 64位系统。所以我们只需要编译出ARM64的动态链接库。
$ cd cryptoauthlib
# 为了目录更清晰,新建一个build目录,所有操作都在这里面完成
$ mkdir build && cd build
# 配置cmake, 使能HAL_I2C接口,ABI选择arm64-v8a
$ cmake -DCMAKE_TOOLCHAIN_FILE="$ANDROID_NDK/build/cmake/android.toolchain.cmake" -DANDROID_ABI="arm64-v8a" -DANDROID_NDK=$ANDROID_NDK -DANDROID_PLATFORM=android-28 -DATCA_HAL_I2C=ON -DATCA_TNGTLS_SUPPORT=ON -DATCA_TFLEX_SUPPORT=ON -DATCA_PRINTF=ON -DATCA_USE_ATCAB_FUNCTIONS=ON ..
$ cmake --build .
其中的cmake参数说明如下:
- CMAKE_TOOLCHAIN_FILE:指定cmake的工具链文件,NDK中已经提供了,直接按地址引用
- ANDROID_ABI:选择使用的ABI, NDK目前支持的有:armeabi-v7a, arm64-v8a, x86, x86_64等。 ARM64位选择arm64-v8a;具体参考NDK ABI
- ANDROID_NDK:设置ANDROID_NDK的路径,这里直接使用前面设置的系统路径
- ANDROID_PLATFORM:设置支持的Android平台,这里android-28是Android 9.0版本。
- ATCA_HAL_I2C=ON:目标板上使用I2C连接到SE,所以要打开这个选项,使能I2C接口。
- ATCA_TNGTLS_SUPPORT=ON:可选项。ATECC608有TNGTLS类型的型号,出厂已经预烧录了TLS的证书链,如果应用中有用到,这里可以打开。
- ATCA_TFLEX_SUPPORT=ON:可选项。ATECC608有TFLEX类型的型号,支持预配置的各种安全应用场景。如果应用中有用到,这里可以打开。
- ATCA_PRINTF=ON:可选项。CAL库中有printf相关的打印API可供调用,如果用到可以打开。
- ATCA_USE_ATCAB_FUNCTIONS=ON:可选项。CAL库中有atcab_前置的api,如果不打开的话,使用的是宏定义calib_前置的api。
如果编译成功的话,在build/lib目录下面会有这个文件:libcryptoauth.so
可以用file命令查看libcryptoauth.so的类型,以确定是否是目标的类型。另外可以用nm命令查看libcryptoauth.so开放的API:
$ file lib/libcryptoauth.so
lib/libcryptoauth.so: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, BuildID[sha1]=6849effc57b942a9dcc8c0d11ab3b333be16f6a8, with debug_info, not stripped
$ nm lib/libcryptoauth.so
......
000000000001731c T atcab_ecdh_tempkey
0000000000017384 T atcab_ecdh_tempkey_ioenc
00000000000173f0 T atcab_gendig
0000000000017460 T atcab_gendivkey
0000000000017554 T atcab_genkey
......
后面就可以在Android Studio的Native C++工程中添加这个so,用JNI C++语言来调用so中的API了。具体参考JNI的开发例程。
4. 在Android Studio中开发APK
整个第4步都是在Windows 11中操作的。
4.1 新建JNI工程
Android Studio中可以通过JNI来调用外部的so中的API。
使用菜单中的新建工程,选择Native C++工程:
输入工程名CAL Test,选择Java,构建配置语言选择Groovy DSL (build.gradle):
Native C++,C++ 标准选择默认就可以了。
点击完成后,打开的工程可以直接构建,通过连接USB到目标板(目标板上需要启用开发者选项,打开USB调试和Root调试)上也可以运行和调试。
4.2 增加Cryptoauth相关配置和代码
4.2.1 复制so到工程中
[个人喜好,可选:将CMakeLists.txt移动到app目录下面]
在app下新建一个jniLibs目录,里面再新建支持的ABI类型,这里新建arm64-v8a目录,把前面生成的libcryptoauth.so文件复制到这里。如果要支持x86_64的ABI,也可以新建一个x86_64目录,把对应的libcryptoauth.so文件复制到这里。目录结构如下:
4.2.2 复制头文件到工程中
在CALTest\app\src\main\cpp中新建一个include目录。
把cryptoauthlib\lib里面的全部头文件复制到include目录中。
修改app目录下的CMakeLists.txt文件:
# 添加自己的库cryptoauth
add_library(cryptoauth SHARED IMPORTED)
set_target_properties(cryptoauth
PROPERTIES
IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/jniLibs/${CMAKE_ANDROID_ARCH_ABI}/libcryptoauth.so)
include_directories(src/main/cpp/include)
# 在最下面的目标链接库中增加cryptoauth
target_link_libraries(${CMAKE_PROJECT_NAME}
# List libraries link to the target library
android
cryptoauth
log)
在app的build.gradle中添加:
plugins {
alias(libs.plugins.android.application)
}
android {
namespace 'com.microchip.caltest'
compileSdk 34
defaultConfig {
applicationId "com.microchip.caltest"
minSdk 29
targetSdk 34
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
# 增加:设置ndk的库和支持的ABI类型
ndk {
moduleName "caltest"
ldLibs "log", "cryptoauth"
abiFilters "arm64-v8a"
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
externalNativeBuild {
cmake {
path file('CMakeLists.txt')
version '3.22.1'
}
}
buildFeatures {
viewBinding true
}
# 增加:生成的APK需要包含库目录
sourceSets {
main {
jniLibs.srcDirs = ['jniLibs']
}
}
ndkVersion '27.0.12077973'
}
dependencies {
implementation libs.appcompat
implementation libs.material
implementation libs.constraintlayout
testImplementation libs.junit
androidTestImplementation libs.ext.junit
androidTestImplementation libs.espresso.core
}
在app\src\main\cpp\native-lib.cpp中修改代码:
#include <jni.h>
#include <string>
#include <android/log.h>
#include "cryptoauthlib.h"
#define TAG "JNI"
//#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, TAG, fmt, ##args)
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args)
ATCAIfaceCfg atecc608_device_desc = {
.iface_type = ATCA_I2C_IFACE,
.devtype = ATECC608,
.atcai2c {
.address = 0xC0,
.bus = 1,
.baud = 100000,
},
.wake_delay = 1500,
.rx_retries = 20,
/*.cfg_data = &Interface*/
};
extern "C" JNIEXPORT jstring JNICALL
Java_com_microchip_caltest_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
extern "C" JNIEXPORT jint JNICALL
Java_com_microchip_caltest_MainActivity_calibInit(
JNIEnv* env,
jobject /* this */) {
(void)env;
ATCA_STATUS status;
uint8_t rev[8];
char strout[1024];
size_t strsize;
LOGD("CAL Init Enter\r\n");
status = atcab_init(&atecc608_device_desc);
if(status != 0)
{
LOGE("CAL Init fail: %d\r\n", status);
}
status = atcab_info(rev);
if(status == 0){
strsize = sizeof(strout);
atcab_bin2hex(rev, 4, strout, &strsize);
LOGE("CAL Rev: %s\r\n", strout);
}
status = atcab_release();
LOGD("CAL Init Exit\r\n");
return status;
}
extern "C" JNIEXPORT jint JNICALL
Java_com_microchip_caltest_MainActivity_ecc608Provision(
JNIEnv* env,
jobject /* this */) {
(void)env;
ATCA_STATUS status;
uint8_t rev[8];
char strout[1024];
size_t strsize;
LOGD("CAL Init Enter\r\n");
status = atcab_init(&atecc608_device_desc);
if(status != 0) {
LOGE("CAL Init fail: %d\r\n", status);
}
status = atcab_info(rev);
if(status == 0) {
strsize = sizeof(strout);
atcab_bin2hex(rev, 4, strout, &strsize);
LOGE("CAL Rev: %s\r\n", strout);
}
status = atcab_release();
LOGD("CAL Init Exit\r\n");
return status;
}
在app\src\main\java\com\microchip\caltest\MainActivity.java中新增代码:
package com.microchip.caltest;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import com.microchip.caltest.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
// Used to load the 'caltest' library on application startup.
static {
System.loadLibrary("caltest");
}
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
// 调用calibInit和ecc608Provision函数
calibInit();
ecc608Provision();
// Example of a call to a native method
TextView tv = binding.sampleText;
tv.setText(stringFromJNI());
}
/**
* A native method that is implemented by the 'caltest' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
public native int calibInit();
public native int ecc608Provision();
}
4.3 在目标板上运行
目标板需要启用开发者模式,并且打开USB调试开关和Root调试开关。
通过USB连接目标板到电脑上,Android Studio会自动连接到板子上,第一次会弹出是否信任此设备,建议勾选。
在Android Studio中打开下方的Terminal工具,
依次运行下面的命令:
$ adb root # 使用root权限连接到adb服务
$ adb shell # 通过adb连接到目标板的shell
rpi4:/ # chmod 777 /dev/i2c-1 # 修改i2c-1的用户读写权限
rpi4:/ # ls -l /dev/i2c-1
crwxrwxrwx 1 root root 89, 1 1970-01-01 08:00 /dev/i2c-1
rpi4:/ #
运行APK前最好确认一下硬件的连接,可以用i2cdetect来检测下:
rpi4:/ # i2cdetect -y -r 1 #查询i2c-1上的设备
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- UU -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: 60 -- -- -- -- -- -- -- UU -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
可以看到,已经检测到器件地址为0x60的器件,注意这里的0x60是7位地址表示的,刚好就是ATECC608的默认器件地址0xC0。说明硬件连接没有问题。
点击运行程序,应该看到Pi 4上显示的画面。
在Logcat中可以看到已经读出了芯片的版本信息:
总结
以上就是,在Android下使用Cryptoauthlib的全部步骤,其它ATECC608的功能都可以参考具体例程实现。