增加 readme
diff --git a/.gitignore b/.gitignore
index 141515e..ff0cd5c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@
*.so
.gradle
build
+.idea
diff --git a/Makefile b/Makefile
index d030da8..9ecdece 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,8 @@
CARGO ?=cargo
RM ?=rm
+NDK ?= $(ANDROID_NDK)
+ANDROID_ARM64_STRIP ?= $(shell $(NDK)/ndk-which --abi arm64-v8a strip)
+ANDROID_ARM_STRIP ?= $(shell $(NDK)/ndk-which --abi armeabi-v7a strip)
.PHONY: clean
@@ -9,7 +12,9 @@
android:
$(CARGO) build --target aarch64-linux-android --release
+ $(ANDROID_ARM64_STRIP) target/aarch64-linux-android/release/libdlsmk.so
$(CARGO) build --target armv7-linux-androideabi --release
+ $(ANDROID_ARM_STRIP) target/armv7-linux-androideabi/release/libdlsmk.so
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..fd32e02
--- /dev/null
+++ b/README.md
@@ -0,0 +1,40 @@
+# 编译环境
+
+ * Android NDK r17 以上版本
+ * rust 1.4 以上版本
+ * rust-ios-android 脚本
+
+
+编译环境安装成功后需要配置 rust 交叉编译环境
+
+## 配置 rust-ios-android
+
+* git clone https://github.com/kennytm/rust-ios-android.git
+
+设置环境变量 ANDROID_HOME 指向 Android SDK 目录, android ndk 应该在 $ANDROID_HOME/ndk-bundles 目录下.
+
+脚本配置环境
+
+```bash
+$ ./create-ndk-standalone.sh
+```
+
+支持成功后会在当前目录下生成 NDK 目录。
+
+## 配置 rust
+修改 $HOME/.cargo/config 文件,增加以下配置
+
+```ini
+[target.aarch64-linux-android]
+ar = "<path-to-your/rust-ios-android/NDK>/arm64/bin/aarch64-linux-android-ar"
+linker = "<path-to-your/rust-ios-android/NDK>/arm64/bin/aarch64-linux-android-clang"
+
+[target.armv7-linux-androideabi]
+ar = "<path-to-your/rust-ios-android/NDK>/arm/bin/arm-linux-androideabi-ar"
+linker = "<path-to-your/rust-ios-android/NDK>/arm/bin/arm-linux-androideabi-clang"
+```
+## 编译 Android 版本
+
+```bash
+$ make android
+```
diff --git a/dlqrcode/src/lib.rs b/dlqrcode/src/lib.rs
index 8a98de0..deb7c96 100644
--- a/dlqrcode/src/lib.rs
+++ b/dlqrcode/src/lib.rs
@@ -62,6 +62,10 @@
}
}
+ fn field_count() -> usize {
+ 5
+ }
+
fn from_qrcode(qr_fields: &Vec<Vec<u8>>) -> Result<Self> {
if qr_fields.len() < 6 {
return Err(DecodeError::new("qrcode fields length must grater than 6."));
@@ -166,6 +170,9 @@
qrdata.push(s);
}
j = i + 1;
+ if qrdata.len() >= DaliQrData::field_count() {
+ break;
+ }
}
}
if j < qrcode.len() {
@@ -173,6 +180,7 @@
s.extend_from_slice(&qrcode[j..]);
qrdata.push(s);
}
+
Ok(())
}
@@ -191,6 +199,8 @@
let totp = self.new_totp();
let time = self.totp_time(secs_offset);
+ let actual = totp.generate(time);
+ debug!("check totp <{}>,<{}>, <{}>", qr_data.totp, time, actual);
if totp.check(&qr_data.totp, time) {
Ok(qr_data)
} else {
@@ -235,17 +245,22 @@
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
- debug!("totp system time : {}, offset {}", time, secs_offset);
- if secs_offset > 0 {
+ let time2 = if secs_offset > 0 {
time + (secs_offset as u64)
} else {
time - (-secs_offset as u64)
- }
+ };
+ debug!(
+ "totp system time : {}, offset {}, actual {}",
+ time, secs_offset, time2
+ );
+ time2
}
fn new_totp(&self) -> TOTP<Vec<u8>> {
let seed = self.totp_seed.clone();
- TOTP::new(Algorithm::SHA1, 8, self.totp_skew, self.totp_step, seed)
+ debug!("totp seed : {}, skew {}, step {}", hex::encode(seed), self.totp_skew, self.totp_step);
+ TOTP::new(Algorithm::SHA1, 8, self.totp_skew, self.totp_step, self.totp_seed.clone())
}
fn encode_qrcode(&self, qr_data: &DaliQrData) -> Result<String> {
@@ -284,6 +299,7 @@
if qr_data.totp.len() == 0 {
let totp = self.new_totp();
let time = self.totp_time(secs_offset);
+ debug!("totp system time : {}", time);
qr_data.totp = totp.generate(time);
}
let sign = self.calc_sign(qr_data);
@@ -295,7 +311,8 @@
pub fn transaction_sign(qrdata: &DaliQrData) -> String {
let sign_str = qrdata.to_qrdata();
- let key = Aes256Cbc::new_var(MASTER.as_bytes(), &MASTER_IV).unwrap();
+ let key = base64::decode(MASTER.as_bytes()).unwrap();
+ let key = Aes256Cbc::new_var(&key, &MASTER_IV).unwrap();
let cipher = key.encrypt_vec(sign_str.as_bytes());
base64::encode_config(cipher, base64::URL_SAFE)
@@ -309,7 +326,8 @@
) -> Result<String> {
let sign = unsafe {
if let Ok(s) = base64::decode_config(sign, base64::URL_SAFE) {
- let key = Aes256Cbc::new_var(MASTER.as_bytes(), &MASTER_IV).unwrap();
+ let key = base64::decode(MASTER.as_bytes()).unwrap();
+ let key = Aes256Cbc::new_var(&key, &MASTER_IV).unwrap();
if let Ok(k) = key.decrypt_vec(&s[..]) {
String::from_utf8_unchecked(k)
} else {
@@ -320,19 +338,40 @@
}
};
+ debug!("transaction sign : {}", sign);
let fields: Vec<&str> = sign.split(QR_FIELD_DILIMITER).collect();
if fields.len() < 4 || fields[1] != cardno {
return Err(DecodeError::new("sign is invalidated"));
}
let tac_buffer = format!("{}{}{}", amount, term_date_time, "{dlsmk}");
+ debug!("transaction sign data : <{}>", tac_buffer);
let mut hasher = Sha256::new();
hasher.update(sign);
hasher.update(tac_buffer.as_bytes());
- let tac = base64::encode_config(hasher.finalize(), base64::URL_SAFE);
+ let tac = base64::encode_config(hasher.finalize(), base64::URL_SAFE_NO_PAD);
Ok(tac)
}
+fn vec_split_once(data: Vec<u8>, delimiter: u8) -> (Option<Vec<u8>>, Option<Vec<u8>>) {
+ let mut pos = 0;
+ while pos < data.len() && data[pos] != delimiter {
+ pos += 1;
+ }
+ if pos == 0 || pos == data.len() {
+ (None, Some(data))
+ } else {
+ let matched : Vec<u8> = data[..pos].to_vec();
+ let n = data.len() - pos;
+ if n == 0 {
+ (Some(matched), None)
+ } else {
+ let left : Vec<u8> = data[pos+1..].to_vec();
+ (Some(matched), Some(left))
+ }
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -346,6 +385,30 @@
}
#[test]
+ fn test_vec() {
+ let s = String::from("2c9cab94737ad0f401737ad1bd2e0000:00015596:80:32125968:ud:23983dslsdlfsddd");
+ let mut v : Vec<u8> = s.as_bytes().to_vec();
+ let delimiter = QR_FIELD_DILIMITER.as_bytes()[0];
+ let mut result = Vec::new();
+ loop {
+ let (matched, left) = vec_split_once(v, delimiter);
+ if let Some(v) = matched {
+ result.push(v);
+ } else {
+ result.push(left.unwrap());
+ break;
+ }
+
+ if left != None {
+ v = left.unwrap();
+ } else {
+ break;
+ }
+ }
+ assert_eq!(result.len(), 6);
+ }
+
+ #[test]
fn aes_test() {
let mut key = [0u8; KEYLEN];
let s = decode("Vbb1syh8U1+CdLmTVGdtDiVvKBQ81n4GmgBEO/ohSbU=").unwrap();
diff --git a/dlsmk/src/lib.rs b/dlsmk/src/lib.rs
index 909632f..c8b6cba 100644
--- a/dlsmk/src/lib.rs
+++ b/dlsmk/src/lib.rs
@@ -13,7 +13,7 @@
let key = "wDp3/3NPEi+R0peokVv010GkDk1mRTp3tUB/lCEVRAA=";
let key_buffer = base64::decode_config(key, base64::STANDARD).unwrap();
let s = hex::encode(key_buffer.as_slice());
- let key_string = hex::decode(format!("{}{:04x}{:02x}", s, 5, 3)).unwrap();
+ let key_string = hex::decode(format!("{}{:04x}{:02x}", s, 5, 10)).unwrap();
println!(
"key : {}",
base64::encode_config(key_string, base64::URL_SAFE)
@@ -115,7 +115,6 @@
}
};
-
match decoder.decode(qrcode.as_ptr(), qrcode.len(), offset as i32) {
Ok(d) => {
put_data(&env, &qrdata, "cardno", &d.cardno);
@@ -183,12 +182,7 @@
return JNI_FALSE;
}
- match dlqrcode::transaction_tac(
- &cardno,
- amount,
- &termDatetime,
- &qrsign
- ) {
+ match dlqrcode::transaction_tac(&cardno, amount, &termDatetime, &qrsign) {
Ok(tac) => {
put_data(&env, &result, "tac", &tac);
JNI_TRUE
diff --git a/java/build.gradle b/java/build.gradle
index 8d5987f..15f4636 100644
--- a/java/build.gradle
+++ b/java/build.gradle
@@ -12,7 +12,8 @@
}
dependencies {
- implementation 'commons-codec:commons-codec:1.15'
+ implementation 'commons-codec:commons-codec:1.15'
+ implementation 'com.eatthepath:java-otp:0.2.0'
}
application {
diff --git a/java/src/main/java/com/supwisdom/dlsmk/DLSMK.java b/java/src/main/java/com/supwisdom/dlsmk/DLSMK.java
index 419d72f..916d7b2 100644
--- a/java/src/main/java/com/supwisdom/dlsmk/DLSMK.java
+++ b/java/src/main/java/com/supwisdom/dlsmk/DLSMK.java
@@ -1,8 +1,15 @@
package com.supwisdom.dlsmk;
-import java.util.Map;
+import com.eatthepath.otp.TimeBasedOneTimePasswordGenerator;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.time.Duration;
+import java.time.Instant;
import java.util.HashMap;
-import org.apache.commons.codec.binary.Base64;
+import java.util.Map;
public class DLSMK {
static byte[] decodeHex(String hex) {
@@ -26,12 +33,14 @@
public static void main(String[] args) {
System.setProperty("java.library.path" , ".");
System.out.println("Version " + DLSMKQrCode.version());
- String key = "wDp3_3NPEi-R0peokVv010GkDk1mRTp3tUB_lCEVRAAABQM";
+ String key = "wDp3_3NPEi-R0peokVv010GkDk1mRTp3tUB_lCEVRAAABQo=";
- String qrcode = "Ntd0wHly2IiweNP61JiewTmZ27JM3Vs-vjZaz45Ly8FoE5mB8QfvJBNJOGYsDQsFBrJqutQxVnyGfWcCbocIbWe2MwOdnfjZLLjAMZKQe_cDKntkfU2rJIo93IyS4TdJ";
+ long systimeMillis = 1604469519L * 1000;
+ String qrcode = "Szgp1QkGFy0DJ6mPVg4wMuOE5XuSe7eZrocdPDAs_iq_H7xzb10vQJwRfk6hn6-_fa6SRKRKd00gExbAShJOTgAj2urAPuQm_NxATJs26I4u6khR74iK7oubHjY1Z6qg";
Map<Object, Object> result = new HashMap();
- if (DLSMKQrCode.decode(key, qrcode, 0L, result)) {
+ long curr = System.currentTimeMillis();
+ if (DLSMKQrCode.decode(key, qrcode, systimeMillis / 1000 - curr / 1000 , result)) {
System.out.println("Decode OK");
String cardno = result.get("cardno").toString();
System.out.format("cardno : %s\n", cardno);
@@ -41,7 +50,10 @@
System.out.println();
result.clear();
- DLSMKQrCode.genTac(sign, cardno, 100, "20201102164539", result);
+ int amount = 100;
+ String dt = "20201102164539";
+ DLSMKQrCode.genTac(sign, cardno, amount, dt, result);
+ System.out.format("transaction amount <%d> datetime <%s>\n", amount, dt);
System.out.format("tac : %s", result.get("tac"));
System.out.println();
} else {