Blog:创建一个Flutter的插件
最近需要在Flutter中实现AES加解密和KDF,但搜索了一下貌似网络上没有现成的库可以用,因此尝试手写了一个Flutter的插件,实现两个功能:
- AES256/CBC/NoPadding 加解密
- Argon2(Argon2d)
插件定义
创建插件工程
其实貌似也可以在Flutter项目中直接调用Platform channel相关的实现,考虑到把这一部分剥离出来可以单独维护和造福后人,还是选择创建一个Plugin。首先需要创建一个插件的工程,通过如下的命令:
flutter create --org com.riguz --template=plugin encryptions
这样会生成一个项目,值得注意的是,这里Android会使用Java,IOS会使用Objective-C。但Objective-C对于我这种没有基础的人来说看着太麻烦了,我尝试了一些之后放弃了。于是需要切换成Swift。这里有一个小的方法可以只修改IOS的部分:
cd encryptions
rm -rf ios examples/ios
flutter create -i swift --org com.riguz .
删除ios的目录后执行这个命令,可以重新生成ios的工程,基于swift的。
定义Dart接口
首先定义出我们要暴露的接口。举个例子,对于AES加密的函数,我们可以这样写:
class Encryptions {
static const MethodChannel _channel = const MethodChannel('encryptions');
static Future<Uint8List> aesEncrypt(
Uint8List key, Uint8List iv, Uint8List value) async {
return await _channel
.invokeMethod("aesEncrypt", {"key": key, "iv": iv, "value": value});
}
这里有几点值得注意的:
- MethodChannel是用来调用原生接口,后面各个平台会注册同名的MethodChannel。
- 调用原生方法通过方法名 + 参数调用,参数的对应列表参见官方文档。这里我们希望的是Java中的byte[] 类型,所以用Uint8List
- 参数通过key-value的map传递到原生接口,原生代码通过参数名取得参数值
Platform实现
ios
首先需要先build一下:
cd encryptions/example; flutter build ios --no-codesign
在Xcode中打开项目,有一个SwiftEncryptionsPlugin的类,在这个里面实现即可:
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let args = call.arguments as! [String: Any];
switch call.method {
case "aesEncrypt", "aesDecrypt":
let key = args["key"] as! FlutterStandardTypedData;
let iv = args["iv"] as! FlutterStandardTypedData;
let value = args["value"] as! FlutterStandardTypedData;
do {
let cipher = try handleAes(key: key.data, iv: iv.data, value: value.data, method: call.method);
result(cipher);
} catch {
result(nil);
};
// ...
}
}
因为需要使用Argon2,需要在swift中调用原生c代码,试了一些办法都不行,后来发现其实比较简单,直接在Supported Files中有一个encryptions-umbrella.h文件中加入引用,就可以直接调用了:
#import "EncryptionsPlugin.h"
#import "argon2.h"
func argon2i(password: Data, salt: Data)-> Data {
var outputBytes = [UInt8](repeating: 0, count: hashLength);
password.withUnsafeBytes { passwordBytes in
salt.withUnsafeBytes {
saltBytes in
argon2i_hash_raw(iterations, memory, parallelism, passwordBytes, password.count, saltBytes, salt.count, &outputBytes, hashLength);
}
}
return Data(bytes: UnsafePointer<UInt8>(outputBytes), count: hashLength);
}
Android
在Android Studio中打开工程(第一次打开是需要build的,```cd encryptions/example; flutter build apk```, ios也类似)。Android中实现起来会简单一点,这里只说一下如何调用c原生代码:
首先在build.gradle中加入额外的步骤:
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
}
}
然后在CMakeLists.txt中指定编译步骤,我这里需要编译一个argon2的库,以及一个JNI调用的库。
add_library(
argon2
SHARED
argon2/src/argon2.c
argon2/src/core.c
argon2/src/blake2/blake2b.c
argon2/src/encoding.c
argon2/src/ref.c
argon2/src/thread.c
)
add_library(
argon2-binding
SHARED
argon2_binding.cpp
)
target_include_directories(
argon2
PRIVATE
argon2/include
)
target_include_directories(
argon2-binding
PRIVATE
argon2/include
)
find_library(
log-lib
log)
target_link_libraries(
native-lib
${log-lib})
target_link_libraries(
argon2-binding
argon2
${log-lib})
然后就通过JNI调用到argon2的方法:
public final class Argon2 {
static {
System.loadLibrary("argon2-binding");
}
// ...
private native byte[] argon2iInternal(int iterations, int memory, int parallelism, final byte[] password, final byte[] salt, int hashLength);
private native byte[] argon2dInternal(int iterations, int memory, int parallelism, final byte[] password, final byte[] salt, int hashLength);
}
详细的代码不再累述。
Example
在example工程中,用dart调用一下这些接口,然后可以分别在Xcode和Android Studio中运行起来,看一下不同平台是否都支持。不清楚是否有自动化的测试方法。
如果想了解更多,这里是详细的代码。
参考: