Browse Source

update keystore

pull/18/head
Sun 4 years ago
parent
commit
fc92c4ec71
  1. 1
      Cargo.toml
  2. 2
      lib/pages/account_generate.dart
  3. 2
      lib/pages/account_restore.dart
  4. 4
      lib/pages/setting/profile.dart
  5. 4
      lib/security.dart
  6. 72
      lib/widgets/show_pin.dart
  7. 1
      pubspec.yaml
  8. 114
      src/account.rs
  9. 11
      src/apps/wallet/models.rs
  10. 11
      src/apps/wallet/rpc.rs
  11. 12
      src/group.rs
  12. 11
      src/rpc.rs
  13. 1
      src/utils.rs
  14. 107
      src/utils/crypto.rs

1
Cargo.toml

@ -27,6 +27,7 @@ once_cell = "1.8"
simplelog = "0.11" simplelog = "0.11"
image = "0.23" image = "0.23"
base64 = "0.13" base64 = "0.13"
hex = "0.4"
sha2 = "0.10" sha2 = "0.10"
blake3 = "1.2" blake3 = "1.2"
bincode = "1.3" bincode = "1.3"

2
lib/pages/account_generate.dart

@ -94,7 +94,7 @@ class _AccountGeneratePageState extends State<AccountGeneratePage> {
Icons.security_rounded, Icons.security_rounded,
title, title,
SetPinWords( SetPinWords(
callback: (key, lock) async { callback: (lock) async {
Navigator.of(context).pop(); Navigator.of(context).pop();
// send to core node service by rpc. // send to core node service by rpc.
final res = await httpPost(Global.httpRpc, final res = await httpPost(Global.httpRpc,

2
lib/pages/account_restore.dart

@ -401,7 +401,7 @@ class _AccountRestorePageState extends State<AccountRestorePage> {
Icons.security_rounded, Icons.security_rounded,
title, title,
SetPinWords( SetPinWords(
callback: (key, lock) async { callback: (lock) async {
Navigator.of(context).pop(); Navigator.of(context).pop();
// send to core node service by rpc. // send to core node service by rpc.
final res = await httpPost(Global.httpRpc, 'account-restore', [ final res = await httpPost(Global.httpRpc, 'account-restore', [

4
lib/pages/setting/profile.dart

@ -250,7 +250,7 @@ class _ProfileDetailState extends State<ProfileDetail> {
context, context,
Icons.security_rounded, Icons.security_rounded,
title, title,
SetPinWords(callback: (key, lock2) async { SetPinWords(callback: (lock2) async {
Navigator.of(context).pop(); Navigator.of(context).pop();
final res = await httpPost(Global.httpRpc, 'account-pin', [lock, lock2]); final res = await httpPost(Global.httpRpc, 'account-pin', [lock, lock2]);
if (res.isOk) { if (res.isOk) {
@ -286,7 +286,7 @@ class _ProfileDetailState extends State<ProfileDetail> {
title, title,
PinWords( PinWords(
hashPin: hash, hashPin: hash,
callback: (_key, _hash) async { callback: (_) async {
Navigator.of(context).pop(); Navigator.of(context).pop();
callback(); callback();
})); }));

4
lib/security.dart

@ -255,9 +255,9 @@ class _SecurityPageState extends State<SecurityPage> {
title, title,
PinWords( PinWords(
hashPin: this._selectedUserLock, hashPin: this._selectedUserLock,
callback: (pinWords, lock) async { callback: (pinWords) async {
Navigator.of(context).pop(); Navigator.of(context).pop();
_verifyAfter(lock); _verifyAfter(pinWords);
})); }));
} }
} }

72
lib/widgets/show_pin.dart

@ -1,9 +1,9 @@
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart' show CupertinoActivityIndicator;
import 'package:esse/l10n/localizations.dart'; import 'package:esse/l10n/localizations.dart';
import 'package:esse/rpc.dart';
import 'package:esse/global.dart';
const pinLength = 6; const pinLength = 6;
@ -66,27 +66,45 @@ class PinWords extends StatefulWidget {
} }
class _PinWordsState extends State<PinWords> { class _PinWordsState extends State<PinWords> {
String pinWords = ''; String _pinWords = '';
bool isError = false; bool _isError = false;
bool _waiting = false;
_checkPin() async {
bool check = false;
final res = await httpPost(Global.httpRpc, 'account-pin-check', [_pinWords]);
if (res.isOk) {
check = res.params[0];
} else {
print(res.error);
}
if (widget.hashPin != "" && !check) {
setState(() {
_waiting = false;
_pinWords = '';
_isError = true;
});
} else {
setState(() {});
widget.callback(_pinWords);
}
}
_inputCallback(String text) { _inputCallback(String text) {
isError = false; if (this._waiting) {
pinWords += text; return;
if (pinWords.length < pinLength) { }
_isError = false;
_pinWords += text;
if (_pinWords.length < pinLength) {
setState(() {}); setState(() {});
} else { } else {
final bytes = utf8.encode(pinWords); setState(() {
final lock = "${sha256.convert(bytes)}"; this._waiting = true;
});
if (widget.hashPin != "" && widget.hashPin != lock) { _checkPin();
setState(() {
pinWords = '';
isError = true;
});
} else {
setState(() {});
widget.callback(pinWords, lock);
}
} }
} }
@ -97,8 +115,8 @@ class _PinWordsState extends State<PinWords> {
Container( Container(
margin: EdgeInsets.all(6.0), margin: EdgeInsets.all(6.0),
child: _circle( child: _circle(
isError, _isError,
i < pinWords.length, i < _pinWords.length,
color.primary, color.primary,
color.primaryVariant, color.primaryVariant,
), ),
@ -114,6 +132,7 @@ class _PinWordsState extends State<PinWords> {
//final lang = AppLocalizations.of(context); //final lang = AppLocalizations.of(context);
return Column(children: [ return Column(children: [
this._waiting ? CupertinoActivityIndicator(radius: 10.0, animating: true) : const SizedBox(),
Container( Container(
margin: const EdgeInsets.only(bottom: 20.0), margin: const EdgeInsets.only(bottom: 20.0),
height: 40, height: 40,
@ -141,9 +160,9 @@ class _PinWordsState extends State<PinWords> {
_keyboradInput(color.primary, color.background, '0', _inputCallback), _keyboradInput(color.primary, color.background, '0', _inputCallback),
GestureDetector( GestureDetector(
onTap: () { onTap: () {
if (pinWords.length > 0) { if (_pinWords.length > 0) {
setState(() { setState(() {
pinWords = pinWords.substring(0, pinWords.length - 1); _pinWords = _pinWords.substring(0, _pinWords.length - 1);
}); });
} }
}, },
@ -213,10 +232,7 @@ class _SetPinWordsState extends State<SetPinWords> {
this._pinWords = ''; this._pinWords = '';
}); });
} else { } else {
setState(() {}); widget.callback(this._pinWords);
final bytes = utf8.encode(this._pinWords);
final lock = "${sha256.convert(bytes)}";
widget.callback(this._pinWords, lock);
} }
} }
} }

1
pubspec.yaml

@ -40,7 +40,6 @@ dependencies:
url: git://github.com/google/flutter-desktop-embedding.git url: git://github.com/google/flutter-desktop-embedding.git
path: plugins/file_selector/file_selector_windows path: plugins/file_selector/file_selector_windows
open_file: any # open file in mobile. open_file: any # open file in mobile.
crypto: any
crop: any crop: any
unorm_dart: any unorm_dart: any
qr_flutter: any qr_flutter: any

114
src/account.rs

@ -1,5 +1,3 @@
use aes_gcm::aead::{generic_array::GenericArray, Aead, NewAead};
use aes_gcm::Aes256Gcm;
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
use tdn::types::{ use tdn::types::{
group::{EventId, GroupId}, group::{EventId, GroupId},
@ -8,6 +6,8 @@ use tdn::types::{
use tdn_did::{generate_id, Keypair, Language}; use tdn_did::{generate_id, Keypair, Language};
use tdn_storage::local::{DStorage, DsValue}; use tdn_storage::local::{DStorage, DsValue};
use crate::utils::crypto::{check_pin, decrypt, decrypt_multiple, encrypt_multiple, hash_pin};
fn mnemonic_lang_to_i64(lang: Language) -> i64 { fn mnemonic_lang_to_i64(lang: Language) -> i64 {
match lang { match lang {
Language::English => 0, Language::English => 0,
@ -48,7 +48,7 @@ pub(crate) struct Account {
pub pass: String, pub pass: String,
pub name: String, pub name: String,
pub avatar: Vec<u8>, pub avatar: Vec<u8>,
pub lock: String, // hashed-key. pub lock: Vec<u8>, // hashed-lock.
pub secret: Vec<u8>, // encrypted value. pub secret: Vec<u8>, // encrypted value.
pub height: u64, pub height: u64,
pub event: EventId, pub event: EventId,
@ -62,7 +62,7 @@ impl Account {
lang: i64, lang: i64,
pass: String, pass: String,
name: String, name: String,
lock: String, lock: Vec<u8>,
avatar: Vec<u8>, avatar: Vec<u8>,
mnemonic: Vec<u8>, mnemonic: Vec<u8>,
secret: Vec<u8>, secret: Vec<u8>,
@ -96,7 +96,7 @@ impl Account {
pub fn generate( pub fn generate(
index: u32, index: u32,
skey: &[u8], // &[u8; 32] salt: &[u8], // &[u8; 32]
lang: i64, lang: i64,
mnemonic: &str, mnemonic: &str,
pass: &str, pass: &str,
@ -112,90 +112,56 @@ impl Account {
if pass.len() > 0 { Some(pass) } else { None }, if pass.len() > 0 { Some(pass) } else { None },
)?; )?;
let cipher = Aes256Gcm::new(GenericArray::from_slice(skey)); // 256-bit key. let mut ebytes = encrypt_multiple(salt, lock, vec![&sk.to_bytes(), mnemonic.as_bytes()])?;
let hash_nonce = blake3::hash(lock.as_bytes()); let mnemonic = ebytes.pop().unwrap_or(vec![]);
let nonce = GenericArray::from_slice(&hash_nonce.as_bytes()[0..12]); // 96-bit key. let secret = ebytes.pop().unwrap_or(vec![]);
let index = index as i64;
let mnemonic_bytes = cipher
.encrypt(nonce, mnemonic.as_bytes())
.map_err(|_e| anyhow!("mnemonic lock invalid."))?;
let sk_bytes = cipher
.encrypt(nonce, sk.to_bytes().as_ref())
.map_err(|_e| anyhow!("secret lock invalid."))?;
Ok(( Ok((
Account::new( Account::new(
gid, gid,
index as i64, index,
lang, lang,
pass.to_string(), pass.to_string(),
name.to_string(), name.to_string(),
lock.to_string(), hash_pin(salt, lock, index),
avatar, avatar,
mnemonic_bytes, mnemonic,
sk_bytes, secret,
), ),
sk, sk,
)) ))
} }
pub fn _check_lock(&self, _lock: &str) -> bool { pub fn check_lock(&self, salt: &[u8], lock: &str) -> Result<()> {
// TODO if check_pin(salt, lock, self.index, &self.lock) {
true Ok(())
} else {
Err(anyhow!("lock is invalid!"))
}
} }
pub fn pin(&mut self, skey: &[u8], old: &str, new: &str) -> Result<()> { pub fn pin(&mut self, salt: &[u8], old: &str, new: &str) -> Result<()> {
self.lock = new.to_string(); self.check_lock(salt, old)?;
let pbytes = decrypt_multiple(salt, old, vec![&self.secret, &self.mnemonic])?;
let cipher = Aes256Gcm::new(GenericArray::from_slice(skey)); // 256-bit key. let mut ebytes = encrypt_multiple(salt, new, pbytes.iter().map(|v| v.as_ref()).collect())?;
self.mnemonic = ebytes.pop().unwrap_or(vec![]);
let hash_old_nonce = blake3::hash(old.as_bytes()); self.secret = ebytes.pop().unwrap_or(vec![]);
let hash_new_nonce = blake3::hash(new.as_bytes()); self.lock = hash_pin(salt, new, self.index);
let old_nonce = GenericArray::from_slice(&hash_old_nonce.as_bytes()[0..12]); // 96-bit key.
let new_nonce = GenericArray::from_slice(&hash_new_nonce.as_bytes()[0..12]); // 96-bit key.
let mnemonic = cipher
.decrypt(old_nonce, self.mnemonic.as_ref())
.map_err(|_e| anyhow!("mnemonic unlock invalid."))?;
self.mnemonic = cipher
.encrypt(new_nonce, mnemonic.as_ref())
.map_err(|_e| anyhow!("mnemonic lock invalid."))?;
let secret = cipher
.decrypt(old_nonce, self.secret.as_ref())
.map_err(|_e| anyhow!("secret unlock invalid."))?;
self.secret = cipher
.encrypt(new_nonce, secret.as_ref())
.map_err(|_e| anyhow!("secret lock invalid."))?;
Ok(()) Ok(())
} }
pub fn mnemonic(&self, skey: &[u8], lock: &str) -> Result<String> { pub fn mnemonic(&self, salt: &[u8], lock: &str) -> Result<String> {
let cipher = Aes256Gcm::new(GenericArray::from_slice(skey)); // 256-bit key. self.check_lock(salt, lock)?;
let hash_nonce = blake3::hash(lock.as_bytes()); let pbytes = decrypt(salt, lock, &self.mnemonic)?;
let nonce = GenericArray::from_slice(&hash_nonce.as_bytes()[0..12]); // 96-bit key. String::from_utf8(pbytes).or(Err(anyhow!("mnemonic unlock invalid.")))
let plaintext = cipher
.decrypt(nonce, self.mnemonic.as_ref())
.map_err(|_e| anyhow!("mnemonic unlock invalid."))?;
String::from_utf8(plaintext).map_err(|_e| anyhow!("mnemonic unlock invalid."))
} }
pub fn secret(&self, skey: &[u8], lock: &str) -> Result<Keypair> { pub fn secret(&self, salt: &[u8], lock: &str) -> Result<Keypair> {
let cipher = Aes256Gcm::new(GenericArray::from_slice(skey)); // 256-bit key. self.check_lock(salt, lock)?;
let hash_nonce = blake3::hash(lock.as_bytes()); let pbytes = decrypt(salt, lock, &self.secret)?;
let nonce = GenericArray::from_slice(&hash_nonce.as_bytes()[0..12]); // 96-bit key. Keypair::from_bytes(&pbytes).or(Err(anyhow!("secret unlock invalid.")))
let plaintext = cipher
.decrypt(nonce, self.secret.as_ref())
.map_err(|_e| anyhow!("secret unlock invalid."))?;
Keypair::from_bytes(&plaintext).map_err(|_e| anyhow!("secret unlock invalid."))
} }
/// here is zero-copy and unwrap is safe. checked. /// here is zero-copy and unwrap is safe. checked.
@ -204,10 +170,10 @@ impl Account {
datetime: v.pop().unwrap().as_i64(), datetime: v.pop().unwrap().as_i64(),
event: EventId::from_hex(v.pop().unwrap().as_str()).unwrap_or(EventId::default()), event: EventId::from_hex(v.pop().unwrap().as_str()).unwrap_or(EventId::default()),
height: v.pop().unwrap().as_i64() as u64, height: v.pop().unwrap().as_i64() as u64,
avatar: base64::decode(v.pop().unwrap().as_string()).unwrap_or(vec![]), avatar: base64::decode(v.pop().unwrap().as_str()).unwrap_or(vec![]),
secret: base64::decode(v.pop().unwrap().as_string()).unwrap_or(vec![]), secret: base64::decode(v.pop().unwrap().as_str()).unwrap_or(vec![]),
mnemonic: base64::decode(v.pop().unwrap().as_string()).unwrap_or(vec![]), mnemonic: base64::decode(v.pop().unwrap().as_str()).unwrap_or(vec![]),
lock: v.pop().unwrap().as_string(), lock: base64::decode(v.pop().unwrap().as_string()).unwrap_or(vec![]),
name: v.pop().unwrap().as_string(), name: v.pop().unwrap().as_string(),
pass: v.pop().unwrap().as_string(), pass: v.pop().unwrap().as_string(),
lang: v.pop().unwrap().as_i64(), lang: v.pop().unwrap().as_i64(),
@ -261,7 +227,7 @@ impl Account {
self.lang, self.lang,
self.pass, self.pass,
self.name, self.name,
self.lock, base64::encode(&self.lock),
base64::encode(&self.mnemonic), base64::encode(&self.mnemonic),
base64::encode(&self.secret), base64::encode(&self.secret),
base64::encode(&self.avatar), base64::encode(&self.avatar),
@ -278,7 +244,7 @@ impl Account {
pub fn update(&self, db: &DStorage) -> Result<usize> { pub fn update(&self, db: &DStorage) -> Result<usize> {
let sql = format!("UPDATE accounts SET name='{}', lock='{}', mnemonic='{}', secret='{}', avatar='{}', height={}, event='{}', datetime={} WHERE id = {}", let sql = format!("UPDATE accounts SET name='{}', lock='{}', mnemonic='{}', secret='{}', avatar='{}', height={}, event='{}', datetime={} WHERE id = {}",
self.name, self.name,
self.lock, base64::encode(&self.lock),
base64::encode(&self.mnemonic), base64::encode(&self.mnemonic),
base64::encode(&self.secret), base64::encode(&self.secret),
base64::encode(&self.avatar), base64::encode(&self.avatar),

11
src/apps/wallet/models.rs

@ -1,5 +1,4 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::ops::AddAssign;
use tdn::types::{ use tdn::types::{
primitive::Result, primitive::Result,
rpc::{json, RpcParam}, rpc::{json, RpcParam},
@ -121,7 +120,7 @@ pub(crate) struct Address {
/// Encrypted secret key. /// Encrypted secret key.
/// if this address is imported, has this field, /// if this address is imported, has this field,
/// if this address is generated, no this field. /// if this address is generated, no this field.
pub secret: String, pub secret: Vec<u8>,
pub balance: String, pub balance: String,
} }
@ -177,13 +176,13 @@ impl Address {
index, index,
address, address,
name: format!("Account {}", index), name: format!("Account {}", index),
secret: "".to_owned(), secret: vec![],
balance: "".to_owned(), balance: "".to_owned(),
id: 0, id: 0,
} }
} }
pub fn import(chain: ChainToken, address: String, secret: String) -> Self { pub fn import(chain: ChainToken, address: String, secret: Vec<u8>) -> Self {
Self { Self {
name: format!("Import {}", &address[2..4]), name: format!("Import {}", &address[2..4]),
chain, chain,
@ -210,7 +209,7 @@ impl Address {
fn from_values(mut v: Vec<DsValue>) -> Self { fn from_values(mut v: Vec<DsValue>) -> Self {
Self { Self {
balance: v.pop().unwrap().as_string(), balance: v.pop().unwrap().as_string(),
secret: v.pop().unwrap().as_string(), secret: base64::decode(v.pop().unwrap().as_str()).unwrap_or(vec![]),
address: v.pop().unwrap().as_string(), address: v.pop().unwrap().as_string(),
name: v.pop().unwrap().as_string(), name: v.pop().unwrap().as_string(),
index: v.pop().unwrap().as_i64(), index: v.pop().unwrap().as_i64(),
@ -226,7 +225,7 @@ impl Address {
self.index, self.index,
self.name, self.name,
self.address, self.address,
self.secret, base64::encode(&self.secret),
self.balance, self.balance,
); );
let id = db.insert(&sql)?; let id = db.insert(&sql)?;

11
src/apps/wallet/rpc.rs

@ -10,10 +10,12 @@ use tdn_storage::local::DStorage;
use tokio::sync::mpsc::Sender; use tokio::sync::mpsc::Sender;
use web3::signing::Key; use web3::signing::Key;
use crate::{rpc::RpcState, storage::wallet_db}; use crate::{rpc::RpcState, storage::wallet_db, utils::crypto::encrypt};
use super::models::{Address, ChainToken, Network, Token}; use super::models::{Address, ChainToken, Network, Token};
const WALLET_DEFAULT_PIN: &'static str = "walletissafe";
#[inline] #[inline]
fn wallet_list(devices: Vec<Address>) -> RpcParam { fn wallet_list(devices: Vec<Address>) -> RpcParam {
let mut results = vec![]; let mut results = vec![];
@ -145,18 +147,17 @@ pub(crate) fn new_rpc_handler(handler: &mut RpcHandler<RpcState>) {
"wallet-import", "wallet-import",
|gid: GroupId, params: Vec<RpcParam>, state: Arc<RpcState>| async move { |gid: GroupId, params: Vec<RpcParam>, state: Arc<RpcState>| async move {
let chain = ChainToken::from_i64(params[0].as_i64().ok_or(RpcError::ParseError)?); let chain = ChainToken::from_i64(params[0].as_i64().ok_or(RpcError::ParseError)?);
let _lock = params[1].as_str().ok_or(RpcError::ParseError)?; let secret = params[1].as_str().ok_or(RpcError::ParseError)?;
let secret = params[2].as_str().ok_or(RpcError::ParseError)?;
let sk: SecretKey = secret.parse().or(Err(RpcError::ParseError))?; let sk: SecretKey = secret.parse().or(Err(RpcError::ParseError))?;
let addr = format!("{:?}", (&sk).address()); let addr = format!("{:?}", (&sk).address());
let group_lock = state.group.read().await; let group_lock = state.group.read().await;
let encrypt_secret = "".to_owned(); let cbytes = encrypt(&group_lock.secret(), WALLET_DEFAULT_PIN, sk.as_ref())?;
let db = wallet_db(group_lock.base(), &gid)?; let db = wallet_db(group_lock.base(), &gid)?;
drop(group_lock); drop(group_lock);
let mut address = Address::import(chain, addr, encrypt_secret); let mut address = Address::import(chain, addr, cbytes);
address.insert(&db)?; address.insert(&db)?;
Ok(HandleResult::rpc(address.to_rpc())) Ok(HandleResult::rpc(address.to_rpc()))
}, },

12
src/group.rs

@ -259,6 +259,10 @@ impl Group {
&self.addr &self.addr
} }
pub fn secret(&self) -> &[u8] {
&self.secret
}
pub fn base(&self) -> &PathBuf { pub fn base(&self) -> &PathBuf {
&self.base &self.base
} }
@ -267,6 +271,14 @@ impl Group {
self.sender.clone() self.sender.clone()
} }
pub fn check_lock(&self, gid: &GroupId, lock: &str) -> bool {
if let Some(account) = self.accounts.get(gid) {
account.check_lock(&self.secret, lock).is_ok()
} else {
false
}
}
pub fn account(&self, gid: &GroupId) -> Result<&Account> { pub fn account(&self, gid: &GroupId) -> Result<&Account> {
if let Some(account) = self.accounts.get(gid) { if let Some(account) = self.accounts.get(gid) {
Ok(account) Ok(account)

11
src/rpc.rs

@ -235,7 +235,7 @@ fn new_rpc_handler(
users.push(vec![ users.push(vec![
gid.to_hex(), gid.to_hex(),
user.name.clone(), user.name.clone(),
user.lock.clone(), base64::encode(&user.lock),
base64::encode(&user.avatar), base64::encode(&user.avatar),
]); ]);
} }
@ -373,6 +373,15 @@ fn new_rpc_handler(
}, },
); );
handler.add_method(
"account-pin-check",
|gid: GroupId, params: Vec<RpcParam>, state: Arc<RpcState>| async move {
let lock = params[0].as_str().ok_or(RpcError::ParseError)?;
let res = state.group.read().await.check_lock(&gid, lock);
Ok(HandleResult::rpc(json!([res])))
},
);
handler.add_method( handler.add_method(
"account-pin", "account-pin",
|gid: GroupId, params: Vec<RpcParam>, state: Arc<RpcState>| async move { |gid: GroupId, params: Vec<RpcParam>, state: Arc<RpcState>| async move {

1
src/utils.rs

@ -1 +1,2 @@
pub(crate) mod crypto;
pub(crate) mod device_status; pub(crate) mod device_status;

107
src/utils/crypto.rs

@ -0,0 +1,107 @@
use aes_gcm::{
aead::{generic_array::GenericArray, Aead, NewAead},
Aes256Gcm,
};
use sha2::{Digest, Sha256};
const FIX_PADDING: [u8; 19] = [
69, 83, 83, 69, 70, 111, 114, 68, 97, 116, 97, 83, 101, 99, 117, 114, 105, 116, 121,
];
/// Hash the given pin.
pub fn hash_pin(salt: &[u8], pin: &str, index: i64) -> Vec<u8> {
let mut hasher = Sha256::new();
hasher.update(salt); // for avoid same hash when no-pin in other derives.
hasher.update(pin.as_bytes());
hasher.update(index.to_le_bytes()); // for avoid same hash when no-pin in one device.
hasher.finalize().to_vec()
}
/// check the pin is the given hash pre-image.
pub fn check_pin(salt: &[u8], pin: &str, index: i64, hash: &[u8]) -> bool {
let mut hasher = Sha256::new();
hasher.update(salt);
hasher.update(pin.as_bytes());
hasher.update(index.to_le_bytes());
let hash_key = hasher.finalize();
&hash_key[..] == hash
}
fn build_cipher(salt: &[u8], pin: &str) -> Aes256Gcm {
let mut hasher = blake3::Hasher::new();
hasher.update(salt);
hasher.update(pin.as_bytes());
hasher.update(&FIX_PADDING);
let hash_key = hasher.finalize();
Aes256Gcm::new(GenericArray::from_slice(hash_key.as_bytes())) // 256-bit key.
}
/// encrypted bytes.
pub fn encrypt(salt: &[u8], pin: &str, ptext: &[u8]) -> anyhow::Result<Vec<u8>> {
let cipher = build_cipher(salt, pin);
let mut nonce = Sha256::new();
nonce.update(pin.as_bytes());
nonce.update(&FIX_PADDING);
let res = nonce.finalize();
let nonce = GenericArray::from_slice(&res[0..12]); // 96-bit key.
cipher
.encrypt(nonce, ptext)
.or(Err(anyhow!("encrypt data failure.")))
}
pub fn encrypt_multiple(salt: &[u8], pin: &str, ptext: Vec<&[u8]>) -> anyhow::Result<Vec<Vec<u8>>> {
let cipher = build_cipher(salt, pin);
let mut nonce = Sha256::new();
nonce.update(pin.as_bytes());
nonce.update(&FIX_PADDING);
let res = nonce.finalize();
let nonce = GenericArray::from_slice(&res[0..12]); // 96-bit key.
let mut ebytes = vec![];
for p in ptext {
ebytes.push(
cipher
.encrypt(nonce, p)
.or(Err(anyhow!("encrypt data failure.")))?,
);
}
Ok(ebytes)
}
/// decrypted bytes.
pub fn decrypt(salt: &[u8], pin: &str, ctext: &[u8]) -> anyhow::Result<Vec<u8>> {
let cipher = build_cipher(salt, pin);
let mut nonce = Sha256::new();
nonce.update(pin.as_bytes());
nonce.update(&FIX_PADDING);
let res = nonce.finalize();
let nonce = GenericArray::from_slice(&res[0..12]); // 96-bit key.
cipher
.decrypt(nonce, ctext)
.or(Err(anyhow!("decrypt data failure.")))
}
pub fn decrypt_multiple(salt: &[u8], pin: &str, ctext: Vec<&[u8]>) -> anyhow::Result<Vec<Vec<u8>>> {
let cipher = build_cipher(salt, pin);
let mut nonce = Sha256::new();
nonce.update(pin.as_bytes());
nonce.update(&FIX_PADDING);
let res = nonce.finalize();
let nonce = GenericArray::from_slice(&res[0..12]); // 96-bit key.
let mut pbytes = vec![];
for c in ctext {
pbytes.push(
cipher
.decrypt(nonce, c)
.or(Err(anyhow!("decrypt data failure.")))?,
);
}
Ok(pbytes)
}
Loading…
Cancel
Save