增加了tac
diff --git a/Makefile b/Makefile
index 7a824c5..d030da8 100644
--- a/Makefile
+++ b/Makefile
@@ -11,5 +11,7 @@
$(CARGO) build --target aarch64-linux-android --release
$(CARGO) build --target armv7-linux-androideabi --release
+
+
clean:
$(RM) -rf target
diff --git a/dlqrcode/Cargo.toml b/dlqrcode/Cargo.toml
index e51e7eb..90ea0f7 100644
--- a/dlqrcode/Cargo.toml
+++ b/dlqrcode/Cargo.toml
@@ -13,8 +13,5 @@
sha2 = "0.9.1"
totp-rs = "0.6.2"
hex = "0.4.2"
-base64-url = "1.4.7"
-
-[dependencies.magic-crypt]
-version = "*"
-default-features = false
+log = { version = "0.4", features = ["max_level_debug", "release_max_level_warn"] }
+simple_logger = "1.11.0"
diff --git a/dlqrcode/src/lib.rs b/dlqrcode/src/lib.rs
index a28a70d..81cbe91 100644
--- a/dlqrcode/src/lib.rs
+++ b/dlqrcode/src/lib.rs
@@ -1,11 +1,14 @@
-extern crate base64_url;
+extern crate base64;
extern crate hex;
+use simple_logger::SimpleLogger;
use std::fmt;
use std::slice;
use std::time::Instant;
use std::time::SystemTime;
+use log::debug;
+
use sha2::{Digest, Sha256};
use aes::Aes256;
@@ -19,6 +22,15 @@
const TOTP_SKEW: u8 = 3;
static QR_FIELD_DILIMITER: &str = ":";
+static MASTER: &str = "wDp3/3NPEi+R0peokVv010GkDk1mRTp3tUB/lCEVRAA=";
+
+static MASTER_IV: [u8; 16] = [0u8; 16];
+
+fn setup_logger() -> bool {
+ SimpleLogger::new().init().unwrap();
+ true
+}
+
pub struct DaliQrCode {
master_key: Vec<u8>,
iv: Vec<u8>,
@@ -69,7 +81,7 @@
self.sign = sign.to_vec();
}
- fn to_qrdata(&self) -> String {
+ pub fn to_qrdata(&self) -> String {
let v = vec![
String::from(&self.uid),
String::from(&self.cardno),
@@ -111,6 +123,8 @@
skew: Option<u8>,
seed: Option<Vec<u8>>,
) -> Result<Self> {
+ setup_logger();
+
let key = key.to_vec();
if key.len() != KEY_LEN {
return Err(DecodeError::new(&format!(
@@ -167,7 +181,7 @@
let mut qr_fields: Vec<Vec<u8>> = Vec::new();
self.split_qrdata(&qrcode, &mut qr_fields)?;
-
+
let qr_data = DaliQrData::from_qrcode(&qr_fields)?;
if qr_data.uid.len() < 16 {
return Err(DecodeError::new("uid must grater than 16"));
@@ -186,18 +200,16 @@
fn check_qrcode_sign(&self, qr_data: &DaliQrData) -> Result<()> {
let sign = self.calc_sign(qr_data);
- if qr_data.sign == sign {
+ debug!("sign expect : {}", hex::encode(sign.clone()));
+ debug!("sign actual : {}", hex::encode(qr_data.sign.clone()));
+ if sign.iter().cloned().eq(qr_data.sign.iter().cloned()) {
Ok(())
} else {
Err(DecodeError::new("sign is invalid"))
}
}
- fn decode_qrcode(
- &self,
- qrcode: *const u8,
- len: usize,
- ) -> Result<Vec<u8>> {
+ fn decode_qrcode(&self, qrcode: *const u8, len: usize) -> Result<Vec<u8>> {
let cipher = match Aes256Cbc::new_var(&self.master_key, &self.iv) {
Ok(c) => c,
Err(e) => return Err(DecodeError::new(&format!("aes key error {:?}", e))),
@@ -205,7 +217,7 @@
let qrcode = unsafe {
let s = slice::from_raw_parts(qrcode, len);
- if let Ok(code) = base64_url::decode(s) {
+ if let Ok(code) = base64::decode_config(s, base64::URL_SAFE) {
code
} else {
return Err(DecodeError::new("data base64 decode error"));
@@ -223,10 +235,11 @@
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
+ debug!("totp system time : {}, offset {}", time, secs_offset);
if secs_offset > 0 {
time + (secs_offset as u64)
} else {
- time - (secs_offset as u64)
+ time - (-secs_offset as u64)
}
}
@@ -246,7 +259,10 @@
buffer.push(QR_FIELD_DILIMITER.as_bytes()[0]);
buffer.extend_from_slice(qr_data.sign.as_slice());
let crypt_data = cipher.encrypt_vec(buffer.as_slice());
- Ok(base64_url::encode(crypt_data.as_slice()))
+ Ok(base64::encode_config(
+ crypt_data.as_slice(),
+ base64::URL_SAFE,
+ ))
}
fn calc_sign(&self, qr_data: &DaliQrData) -> Vec<u8> {
@@ -255,6 +271,7 @@
hasher.update(qr_data.uid.as_bytes());
let salt = hasher.finalize();
let mut hasher = Sha256::new();
+ debug!("qrdata sign : {}", qr_data.to_qrdata());
hasher.update(qr_data.to_qrdata().as_bytes());
hasher.update(salt);
hasher.finalize().to_vec()
@@ -271,16 +288,60 @@
}
let sign = self.calc_sign(qr_data);
qr_data.update_sign(&sign);
+ debug!("encode qrcode sign : {}", hex::encode(sign));
self.encode_qrcode(qr_data)
}
}
+
+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 cipher = key.encrypt_vec(sign_str.as_bytes());
+ base64::encode_config(cipher, base64::URL_SAFE)
+}
+
+pub fn transaction_tac(
+ cardno: &str,
+ amount: i32,
+ term_date_time: &str,
+ sign: &str,
+) -> 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();
+ if let Ok(k) = key.decrypt_vec(&s[..]) {
+ String::from_utf8_unchecked(k)
+ } else {
+ return Err(DecodeError::new("sign data error"));
+ }
+ } else {
+ return Err(DecodeError::new("sign format invalid"));
+ }
+ };
+
+ 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}");
+ let mut hasher = Sha256::new();
+ hasher.update(sign);
+ hasher.update(tac_buffer.as_bytes());
+ let tac = base64::encode_config(hasher.finalize(), base64::URL_SAFE);
+ Ok(tac)
+}
+
+
#[cfg(test)]
mod tests {
use super::*;
use base64::decode;
use std::convert::TryInto;
const KEYLEN: usize = 32;
+
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
@@ -321,7 +382,7 @@
fn get_key() -> ([u8; KEYLEN], [u8; 16]) {
let mut key = [0u8; KEYLEN];
- let s = decode("Vbb1syh8U1+CdLmTVGdtDiVvKBQ81n4GmgBEO/ohSbU=").unwrap();
+ let s = base64::decode("wDp3/3NPEi+R0peokVv010GkDk1mRTp3tUB/lCEVRAA=").unwrap();
key.clone_from_slice(&s.as_slice()[..KEYLEN]);
let iv: [u8; 16] = {
@@ -363,25 +424,58 @@
}
}
- #[test]
- fn check_qrcode_decode() {
- let (key, iv) = get_key();
- let mut qr_data = DaliQrData::new();
+ fn get_qrdata(qr_data: &mut DaliQrData) -> () {
qr_data.uid = String::from("0a5de6ce985d43989b7ebe64ad8eb9c3");
qr_data.cardno = String::from("00001252");
qr_data.cardtype = String::from("80");
+ }
- let dali_qrcode = DaliQrCode::new(key, Some(iv), Some(60u64), Some(5u8), None).unwrap();
+ fn test_qr_decoder(decoder: &DaliQrCode, qrcode: &str, offset: i32) -> bool {
+ match decoder.decode(qrcode.as_ptr(), qrcode.len(), offset) {
+ Ok(_) => true,
+ Err(_) => false,
+ }
+ }
+
+ #[test]
+ fn check_qrcode_decode() {
+ let (key, iv) = get_key();
+
+ let mut qr_data = DaliQrData::new();
+ get_qrdata(&mut qr_data);
+
+ let dali_qrcode = DaliQrCode::new(key, Some(iv), Some(30u64), Some(3u8), None).unwrap();
match dali_qrcode.encode(&mut qr_data, 0) {
Ok(qrcode) => {
- if let Err(e) = dali_qrcode.decode(qrcode.as_ptr(), qrcode.len(), 0) {
- panic!("error {}", e);
- }
+ // println!("qrdata : {:?}", qr_data);
+ debug!("encode qrcode : {}", qrcode);
+ assert_eq!(test_qr_decoder(&dali_qrcode, &qrcode, -20), true);
+ assert_eq!(test_qr_decoder(&dali_qrcode, &qrcode, -300), false);
+ assert_eq!(test_qr_decoder(&dali_qrcode, &qrcode, 49), true);
+ assert_eq!(test_qr_decoder(&dali_qrcode, &qrcode, 65), true);
}
Err(e) => {
panic!("error {}", e);
}
}
}
+
+ #[test]
+ fn test_tac() {
+ let qrcode = "Ntd0wHly2IiweNP61JiewTmZ27JM3Vs-vjZaz45Ly8G1_lgcdkMw1QClLfKm-9pVTvT0pvrfhMpRgvh9UcQ26UYibVeczWYMtatN4x1OlsGM2cKXooCT1d-ika480wWq";
+ let tac = "8tck8-ljatwwcbtzlgty3l-tq7td3evbjvnzkuusrew";
+
+ let (key, iv) = get_key();
+ let decoder = DaliQrCode::new(key, Some(iv), Some(30u64), Some(3u8), None).unwrap();
+ match decoder.decode(qrcode.as_bytes().as_ptr(), qrcode.len(), -300) {
+ Ok(data) => {
+ let sign = transaction_sign(&data);
+ let actual_tac =
+ transaction_tac(&data.cardno, 100, "20201102135834", &sign).unwrap();
+ assert_eq!(actual_tac, tac);
+ }
+ Err(e) => panic!("qrcode error {:?}", e),
+ }
+ }
}
diff --git a/dlsmk/Cargo.toml b/dlsmk/Cargo.toml
index 48e7107..eb237b5 100644
--- a/dlsmk/Cargo.toml
+++ b/dlsmk/Cargo.toml
@@ -17,3 +17,5 @@
dlqrcode = { path = "../dlqrcode" }
libc = "0.2.80"
hex = "0.4.2"
+base64 = "0.13.0"
+log = { version = "0.4", features = ["max_level_debug", "release_max_level_warn"] }
diff --git a/dlsmk/src/lib.rs b/dlsmk/src/lib.rs
index fa982de..709b240 100644
--- a/dlsmk/src/lib.rs
+++ b/dlsmk/src/lib.rs
@@ -1,9 +1,24 @@
+extern crate base64;
+extern crate hex;
+
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
+
+ #[test]
+ fn test_key() {
+ 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, 30, 5)).unwrap();
+ println!(
+ "key : {}",
+ base64::encode_config(key_string, base64::URL_SAFE)
+ );
+ }
}
// use std::ffi::{CStr, CString};
@@ -18,20 +33,21 @@
// CString::new("Hello ".to_owned()).unwrap().into_raw()
// }
-// #[cfg(target_os = "android")]
#[cfg(any(target_family = "unix", target_os = "android"))]
-// #[cfg(target_family = "unix")]
#[allow(non_snake_case)]
pub mod android {
+ extern crate base64;
extern crate jni;
use self::jni::objects::{JClass, JMap, JObject, JString};
- use self::jni::sys::{jboolean, jlong, JNI_FALSE, JNI_TRUE};
+ use self::jni::sys::{jboolean, jint, jlong, JNI_FALSE, JNI_TRUE};
use self::jni::JNIEnv;
// use super::*;
use libc;
use std::slice;
+ use log::debug;
+
use dlqrcode::DaliQrCode;
fn put_data(env: &JNIEnv, map: &JMap, key: &str, value: &str) {
@@ -75,11 +91,17 @@
let qrdata = env.get_map(result).expect("invalid qrdata map");
- let key = {
+ let (key, step, skew) = {
let mut k = [0u8; 32];
- if let Ok(v) = hex::decode(key.as_slice()) {
- k.clone_from_slice(v.as_slice());
- k
+ if let Ok(v) = base64::decode_config(key.as_slice(), base64::URL_SAFE) {
+ if v.len() != 35 {
+ put_data(&env, &qrdata, "error", "key format error");
+ return JNI_FALSE;
+ }
+ k.clone_from_slice(&v.as_slice()[..32]);
+ let step = ((v[32] as u64) << 8) | (v[33] as u64);
+ let skew = v[34] as u8;
+ (k, step, skew)
} else {
put_data(
&env,
@@ -91,7 +113,8 @@
}
};
- let decode = match DaliQrCode::new(key, None, None, None, None) {
+ debug!("TOTP step <{}> , skew <{}>", step, skew);
+ let decoder = match DaliQrCode::new(key, None, Some(step), Some(skew), None) {
Ok(d) => d,
Err(e) => {
let s = format!("invalid input parameter {:?}", e);
@@ -100,26 +123,13 @@
}
};
- match decode.decode(qrcode.as_ptr(), qrcode.len(), offset as i32) {
+ match decoder.decode(qrcode.as_ptr(), qrcode.len(), offset as i32) {
Ok(d) => {
- qrdata
- .put(
- *env.new_string("cardno").unwrap(),
- *env.new_string(d.cardno).unwrap(),
- )
- .unwrap();
- qrdata
- .put(
- *env.new_string("cardtype").unwrap(),
- *env.new_string(d.cardtype).unwrap(),
- )
- .unwrap();
- qrdata
- .put(
- *env.new_string("uid").unwrap(),
- *env.new_string(d.uid).unwrap(),
- )
- .unwrap();
+ put_data(&env, &qrdata, "cardno", &d.cardno);
+ put_data(&env, &qrdata, "cardtype", &d.cardtype);
+ put_data(&env, &qrdata, "uid", &d.uid);
+ let sign = dlqrcode::transaction_sign(&d);
+ put_data(&env, &qrdata, "sign", &sign);
return JNI_TRUE;
}
Err(e) => {
@@ -129,4 +139,56 @@
}
};
}
+
+ #[no_mangle]
+ pub unsafe extern "C" fn Java_com_supwisdom_dlsmk_DLSMKQrCode_genTac(
+ env: JNIEnv,
+ _: JClass,
+ qrsign: JString,
+ cardno: JString,
+ amount: jint,
+ termDatetime: JString,
+ result: JObject,
+ ) -> jboolean {
+ let qrsign = {
+ let s = env.get_string(qrsign).expect("invalid key string").as_ptr();
+ let keylen = libc::strlen(s);
+ String::from_raw_parts(s as *mut u8, keylen, keylen)
+ };
+ let cardno = {
+ let s = env
+ .get_string(cardno)
+ .expect("invalid cardno string")
+ .as_ptr();
+ let len = libc::strlen(s);
+ String::from_raw_parts(s as *mut u8, len, len)
+ };
+
+ let termDatetime = {
+ let s = env
+ .get_string(termDatetime)
+ .expect("invalid datetime")
+ .as_ptr();
+ let len = libc::strlen(s);
+ String::from_raw_parts(s as *mut u8, len, len)
+ };
+
+ let result = env.get_map(result).expect("invalid qrdata map");
+
+ if termDatetime.len() != 14 {
+ put_data(&env, &result, "error", "term datetime must be 14 chars");
+ return JNI_FALSE;
+ }
+
+ match dlqrcode::transaction_tac(&cardno, amount, &termDatetime, &qrsign) {
+ Ok(tac) => {
+ put_data(&env, &result, "tac", &tac);
+ JNI_TRUE
+ }
+ Err(e) => {
+ put_data(&env, &result, "error", &format!("{:?}", e));
+ JNI_FALSE
+ }
+ }
+ }
}
diff --git a/java/src/main/java/com/supwisdom/dlsmk/DLSMK.java b/java/src/main/java/com/supwisdom/dlsmk/DLSMK.java
index 219284f..9921c52 100644
--- a/java/src/main/java/com/supwisdom/dlsmk/DLSMK.java
+++ b/java/src/main/java/com/supwisdom/dlsmk/DLSMK.java
@@ -25,15 +25,23 @@
public static void main(String[] args) {
System.setProperty("java.library.path" , ".");
- String key = encodeHex(Base64.decodeBase64("Vbb1syh8U1+CdLmTVGdtDiVvKBQ81n4GmgBEO/ohSbU="));
+ String key = "wDp3_3NPEi-R0peokVv010GkDk1mRTp3tUB_lCEVRAAAHgU=";
- String qrcode = "6lHyFX_vg5U2hymn8OsdNUD7dT0-sCmEQkKrm9cnzHlku6-FYxuL6nP5YR2Fve8Sfj-Asd-3dfQUkaiqqbfQWO8B_811B3uhHmGm9IjlpLicz_c1H1_ORb9tJl-IhMKu";
+ String qrcode = "Ntd0wHly2IiweNP61JiewTmZ27JM3Vs-vjZaz45Ly8FoE5mB8QfvJBNJOGYsDQsFBrJqutQxVnyGfWcCbocIbWe2MwOdnfjZLLjAMZKQe_cDKntkfU2rJIo93IyS4TdJ";
Map<Object, Object> result = new HashMap();
if (DLSMKQrCode.decode(key, qrcode, 0L, result)) {
System.out.println("Decode OK");
- System.out.format("cardno : %s", result.get("cardno").toString());
- System.out.format("cardtype : %s", result.get("cardtype").toString());
+ String cardno = result.get("cardno").toString();
+ System.out.format("cardno : %s\n", cardno);
+ System.out.format("cardtype : %s\n", result.get("cardtype").toString());
+ String sign = result.get("sign").toString();
+ System.out.format("sign : %s", sign);
+ System.out.println();
+
+ result.clear();
+ DLSMKQrCode.genTac(sign, cardno, 100, "20201102164539", result);
+ System.out.format("tac : %s", result.get("tac"));
System.out.println();
} else {
System.out.format("decode failed: %s", result.get("error").toString());
diff --git a/java/src/main/java/com/supwisdom/dlsmk/DLSMKQrCode.java b/java/src/main/java/com/supwisdom/dlsmk/DLSMKQrCode.java
index 257ba7b..c048c46 100644
--- a/java/src/main/java/com/supwisdom/dlsmk/DLSMKQrCode.java
+++ b/java/src/main/java/com/supwisdom/dlsmk/DLSMKQrCode.java
@@ -7,12 +7,24 @@
System.loadLibrary("dlsmk");
}
/**
- * @param keyHex - 主密钥, HEX 格式
+ * @param keyHex - 主密钥,base64编码
* @param qrcodeBase64 - 二维码数据
* @param secsOffset - 市民卡平台与App的时间差,单位秒,可以为负数
- * @param result - 结果 , key in [cardno, cardtype, error]
+ * @param result - 结果 , key in [cardno, cardtype, sign, error]
* @return - true 成功,false 失败
*/
- public static native boolean decode(String keyHex,String qrcodeBase64,
- Long secsOffset, Map<Object, Object> result);
+ public static native boolean decode(String key,String qrcodeBase64,
+ long secsOffset, Map<Object, Object> result);
+
+
+ /**
+ * @param sign - 签名解码二维码时获取到的签名
+ * @param cardno - 市民卡号
+ * @param amount -交金额, 单位 分
+ * @param termDatetime - 终端时间 例如: 20201023153948
+ * @param result j结果 , key in [tac, error]
+ * @return -
+ */
+ public static native boolean genTac(String sign, String cardno, int amount,
+ String termDatetime, Map<Object, Object> result);
}