Skip to content

Commit bd3f65e

Browse files
authored
Merge pull request #1 from replydev/argon2id
Use Argon2id for key derivation
2 parents 2f15c08 + 4025afd commit bd3f65e

File tree

4 files changed

+52
-24
lines changed

4 files changed

+52
-24
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11

22
# cotp - command line totp authenticator
3+
[![Actions Status](https://github.com/replydev/cotp/workflows/Rust/badge.svg)]("https://github.com/replydev/cotp/actions")
34

45
I believe that security is of paramount importance, especially in this digital world. I created cotp because I needed a minimalist, secure, desktop accessible software to manage my two-factor authentication codes.
56

@@ -79,7 +80,7 @@ You will find the compiled binary in **target/release** folder
7980
## Planned features
8081

8182
- [x] Reduce binary size and improve compilation speed by removing useless dependencies.
82-
- [ ] Use argon2id13 for key derivation
83+
- [x] Use argon2id13 for key derivation
8384
- [ ] Backup compatibility with:
8485
- [x] Aegis
8586
- [x] andOTP

src/argument_functions.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ pub fn help(){
77
println!("-a,--add <secret> <issuer> <label> | Add a new OTP code");
88
println!("-r,--remove <secret> <issuer> <label> | Remove an OTP code");
99
println!("-e,--edit <id> <secret> <issuer> <label> | Edit an OTP code");
10-
println!("-i,--import aegis,andotp <filename> | Import a backup from a given application");
10+
println!("-i,--import <appname> <path> | Import a backup from a given application");
1111
println!("-ex,--export | Export the entire database in a plaintext json format");
1212
println!("-j,--json | Print results in json format");
1313
println!("-h,--help | Print this help");
@@ -19,7 +19,7 @@ pub fn import(args: Vec<String>){
1919
let elements: Vec<database_loader::OTPElement>;
2020

2121
match &args[2][..]{
22-
"andotp" => result = importers::and_otp::import(&args[3]),
22+
"cotp" | "andotp" => result = importers::and_otp::import(&args[3]),
2323
"aegis" => result = importers::aegis::import(&args[3]),
2424
_=> {
2525
println!("Invalid argument: {}", &args[2]);
@@ -38,7 +38,11 @@ pub fn import(args: Vec<String>){
3838
println!("Successfully imported database");
3939
}
4040
else{
41-
println!("Invalid arguments, type cotp --import <backup_format> <path>");
41+
println!("Invalid arguments, type cotp --import <appname> <path>");
42+
println!("cotp can import backup from:");
43+
println!("\"cotp\"");
44+
println!("\"aegis\"");
45+
println!("\"andotp\"");
4246
}
4347
}
4448

src/cryptograpy.rs

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,26 @@ impl fmt::Display for CoreError {
2323

2424
impl error::Error for CoreError {}
2525

26-
pub fn encrypt_string(plaintext: &mut String,password: &str) -> String {
26+
fn argon_derive_key(key: &mut[u8;32],password_bytes: &[u8],salt: &pwhash::argon2id13::Salt) -> Result<Key,String>{
27+
let result = pwhash::argon2id13::derive_key(key, password_bytes, salt,
28+
pwhash::argon2id13::OPSLIMIT_INTERACTIVE,
29+
pwhash::argon2id13::MEMLIMIT_INTERACTIVE);
30+
match result{
31+
Ok(&[u8]) => Ok(Key(*key)),
32+
Ok(&[]) => Ok(Key(*key)),
33+
Ok(&[_,_,..]) =>Ok(Key(*key)),
34+
Err(()) => Err(String::from("Failed to derive encryption key"))
35+
}
36+
}
37+
38+
pub fn encrypt_string(plaintext: String,password: &str) -> String {
2739
let mut encrypted = String::new();
2840
encrypted.push_str(&base64::encode(SIGNATURE));
2941
encrypted.push('|');
30-
let salt = pwhash::gen_salt();
42+
let salt = pwhash::argon2id13::gen_salt();
3143
encrypted.push_str(&base64::encode(salt.0));
3244
encrypted.push('|');
33-
let mut key = [0u8; KEYBYTES];
34-
pwhash::derive_key(&mut key, password.as_bytes(), &salt,
35-
pwhash::OPSLIMIT_INTERACTIVE,
36-
pwhash::MEMLIMIT_INTERACTIVE).unwrap();
37-
let key = Key(key);
38-
45+
let key = argon_derive_key(&mut [0u8; KEYBYTES],password.as_bytes(),&salt).unwrap();
3946
let (mut enc_stream, header) = Stream::init_push(&key).unwrap();
4047

4148
encrypted.push_str(&base64::encode(header.0));
@@ -47,19 +54,19 @@ pub fn encrypt_string(plaintext: &mut String,password: &str) -> String {
4754
encrypted
4855
}
4956

50-
pub fn decrypt_string(encrypted_text: &mut str,password: &str) -> Result<String, String> {
57+
pub fn decrypt_string(encrypted_text: &str,password: &str) -> Result<String, String> {
5158
let split = encrypted_text.split('|');
5259
let vec: Vec<&str> = split.collect();
5360
let byte_salt = base64::decode(vec[1]).unwrap();
54-
let salt = pwhash::Salt(byte_vec_to_byte_array(byte_salt));
61+
let salt = pwhash::argon2id13::Salt(byte_vec_to_byte_array(byte_salt));
5562
let byte_header = base64::decode(vec[2]).unwrap();
5663
let header = Header(header_vec_to_header_array(byte_header));
5764
let cipher = base64::decode(vec[3]).unwrap();
5865

5966
let mut key = [0u8; KEYBYTES];
60-
pwhash::derive_key(&mut key, password.as_bytes(), &salt,
61-
pwhash::OPSLIMIT_INTERACTIVE,
62-
pwhash::MEMLIMIT_INTERACTIVE)
67+
pwhash::argon2id13::derive_key(&mut key, password.as_bytes(), &salt,
68+
pwhash::argon2id13::OPSLIMIT_INTERACTIVE,
69+
pwhash::argon2id13::MEMLIMIT_INTERACTIVE)
6370
.map_err(|_| CoreError::new("Deriving key failed")).unwrap();
6471
let key = Key(key);
6572

@@ -74,9 +81,9 @@ pub fn decrypt_string(encrypted_text: &mut str,password: &str) -> Result<String,
7481
Ok(String::from_utf8(decrypted).unwrap())
7582
}
7683

77-
fn byte_vec_to_byte_array(byte_vec: Vec<u8>) -> [u8;32]{
84+
fn byte_vec_to_byte_array(byte_vec: Vec<u8>) -> [u8;16]{
7885
byte_vec.try_into()
79-
.unwrap_or_else(|v: Vec<u8>| panic!("Expected a Vec of length {} but it was {}", 32, v.len()))
86+
.unwrap_or_else(|v: Vec<u8>| panic!("Expected a Vec of length {} but it was {}", 16, v.len()))
8087
}
8188

8289
fn header_vec_to_header_array(byte_vec: Vec<u8>) -> [u8;24]{
@@ -93,4 +100,20 @@ pub fn prompt_for_passwords(message: &str) -> String{
93100
}
94101
}
95102
password
103+
}
104+
105+
106+
#[cfg(test)]
107+
mod tests {
108+
use super::{encrypt_string,decrypt_string};
109+
#[test]
110+
fn test_encryption() {
111+
assert_eq!(
112+
String::from("Secret data@#[]ò"),
113+
decrypt_string(
114+
&mut encrypt_string(String::from("Secret data@#[]ò"),"pa$$w0rd"),
115+
"pa$$w0rd"
116+
).unwrap()
117+
);
118+
}
96119
}

src/database_loader.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ impl OTPElement {
6767
}
6868

6969
pub fn read_from_file() -> Result<Vec<OTPElement>,String>{
70-
let mut encrypted_contents = read_to_string(&get_db_path()).unwrap();
70+
let encrypted_contents = read_to_string(&get_db_path()).unwrap();
7171
//rust close files at the end of the function
72-
let contents = cryptograpy::decrypt_string(&mut encrypted_contents, &cryptograpy::prompt_for_passwords("Password: "));
72+
let contents = cryptograpy::decrypt_string(&encrypted_contents, &cryptograpy::prompt_for_passwords("Password: "));
7373
match contents {
7474
Ok(contents) => {
7575
let vector: Vec<OTPElement> = serde_json::from_str(&contents).unwrap();
@@ -172,8 +172,8 @@ pub fn export_database() -> Result<String, String> {
172172
let mut exported_path = utils::get_home_folder().to_str().unwrap().to_string();
173173
exported_path.push_str("/exported.cotp");
174174
let mut file = File::create(&exported_path).expect("Cannot create file");
175-
let mut encrypted_contents = read_to_string(&get_db_path()).unwrap();
176-
let contents = cryptograpy::decrypt_string(&mut encrypted_contents, &cryptograpy::prompt_for_passwords("Password: "));
175+
let encrypted_contents = read_to_string(&get_db_path()).unwrap();
176+
let contents = cryptograpy::decrypt_string(&encrypted_contents, &cryptograpy::prompt_for_passwords("Password: "));
177177
match contents {
178178
Ok(contents) => {
179179
file.write_all(contents.as_bytes()).expect("Failed to write contents");
@@ -191,7 +191,7 @@ pub fn overwrite_database(elements: Vec<OTPElement>){
191191
}
192192

193193
pub fn overwrite_database_json(json: &str){
194-
let encrypted = cryptograpy::encrypt_string(&mut json.to_string(), &cryptograpy::prompt_for_passwords("Insert password for database encryption: "));
194+
let encrypted = cryptograpy::encrypt_string(json.to_string(), &cryptograpy::prompt_for_passwords("Insert password for database encryption: "));
195195
utils::write_to_file(&encrypted, &mut File::create(utils::get_db_path()).expect("Failed to open file"));
196196
}
197197

0 commit comments

Comments
 (0)