Skip to content

Commit 54b2b45

Browse files
committed
Increase the bcrypt cost to 12
1 parent 4e90145 commit 54b2b45

File tree

6 files changed

+197
-117
lines changed

6 files changed

+197
-117
lines changed

build.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
use std::{env, error::Error, fmt, path::Path};
2+
3+
fn main() -> Result<(), RklBuildError> {
4+
let out_dir = env::var("OUT_DIR")?;
5+
generate_version(&out_dir)?;
6+
Ok(())
7+
}
8+
9+
fn generate_version(out_dir: &str) -> Result<(), RklBuildError> {
10+
let dest_path = Path::new(&out_dir).join("rkl_version.rs");
11+
let version = env!("CARGO_PKG_VERSION");
12+
let contents = format!(
13+
"
14+
pub(crate) fn rkl_version() -> &'static str {{
15+
\"{version}\"
16+
}}
17+
"
18+
);
19+
std::fs::write(dest_path, contents)?;
20+
Ok(())
21+
}
22+
23+
#[derive(Debug)]
24+
struct RklBuildError {
25+
description: String,
26+
}
27+
28+
impl fmt::Display for RklBuildError {
29+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
30+
write!(f, "{}", self.description)
31+
}
32+
}
33+
34+
impl Error for RklBuildError {
35+
fn description(&self) -> &str {
36+
self.description.as_str()
37+
}
38+
}
39+
40+
impl From<std::env::VarError> for RklBuildError {
41+
fn from(err: std::env::VarError) -> RklBuildError {
42+
RklBuildError {
43+
description: format!("{:?}", err),
44+
}
45+
}
46+
}
47+
48+
impl From<std::io::Error> for RklBuildError {
49+
fn from(err: std::io::Error) -> RklBuildError {
50+
RklBuildError {
51+
description: format!("{:?}", err),
52+
}
53+
}
54+
}

src/api/mod.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,13 +467,16 @@ pub struct Props {
467467
idle_timeout_seconds: isize,
468468
/// The count of the words that comprise the generated passphraases
469469
generated_passphrases_words_count: isize,
470+
/// The current version
471+
version: String,
470472
}
471473

472474
impl Default for Props {
473475
fn default() -> Self {
474476
Props {
475477
idle_timeout_seconds: 1800,
476478
generated_passphrases_words_count: 5,
479+
version: "".to_string(),
477480
}
478481
}
479482
}
@@ -482,10 +485,13 @@ impl Props {
482485
pub(crate) fn new(
483486
idle_timeout_seconds: isize,
484487
generated_passphrases_words_count: isize,
488+
version: &str,
485489
) -> Props {
490+
let version = version.to_string();
486491
Props {
487492
idle_timeout_seconds,
488493
generated_passphrases_words_count,
494+
version,
489495
}
490496
}
491497

@@ -498,10 +504,15 @@ impl Props {
498504
.get("generated_passphrases_words_count")
499505
.and_then(|value| value.as_integer().and_then(|v| Some(v as isize)))
500506
.unwrap_or_else(|| Props::default().generated_passphrases_words_count());
507+
let version = table
508+
.get("version")
509+
.and_then(|value| value.as_str().and_then(|v| Some(v.to_string())))
510+
.unwrap_or_else(|| Props::default().version().to_string());
501511

502512
Ok(Self::new(
503513
idle_timeout_seconds,
504514
generated_passphrases_words_count,
515+
&version,
505516
))
506517
}
507518

@@ -516,6 +527,10 @@ impl Props {
516527
"generated_passphrases_words_count".to_string(),
517528
toml::Value::Integer(self.generated_passphrases_words_count as i64),
518529
);
530+
table.insert(
531+
"version".to_string(),
532+
toml::Value::String(self.version().to_string()),
533+
);
519534

520535
table
521536
}
@@ -527,6 +542,25 @@ impl Props {
527542
pub fn generated_passphrases_words_count(&self) -> isize {
528543
self.generated_passphrases_words_count
529544
}
545+
546+
pub fn version(&self) -> &str {
547+
&self.version
548+
}
549+
550+
#[allow(dead_code)]
551+
pub fn set_idle_timeout_seconds(&mut self, new_timeout: isize) {
552+
self.idle_timeout_seconds = new_timeout
553+
}
554+
555+
#[allow(dead_code)]
556+
pub fn set_generated_passphrases_words_count(&mut self, new_generated_passphrases_words_count: isize) {
557+
self.generated_passphrases_words_count = new_generated_passphrases_words_count;
558+
}
559+
560+
pub fn set_version(&mut self, new_version: &str) {
561+
self.version = new_version.to_string();
562+
}
563+
530564
}
531565

532566
/// Enumeration of the several different Menus that an `Editor` implementation should handle.
@@ -1064,6 +1098,7 @@ mod api_unit_tests {
10641098
let toml = r#"
10651099
idle_timeout_seconds = 33
10661100
generated_passphrases_words_count = 5
1101+
version = "0.0.0"
10671102
"#;
10681103

10691104
let value = toml.parse::<toml::value::Value>().unwrap();
@@ -1073,6 +1108,7 @@ mod api_unit_tests {
10731108
let props = props_opt.unwrap();
10741109
assert!(props.idle_timeout_seconds() == 33);
10751110
assert!(props.generated_passphrases_words_count() == 5);
1111+
assert!(props.version() == "0.0.0");
10761112
}
10771113

10781114
#[test]
@@ -1098,6 +1134,18 @@ mod api_unit_tests {
10981134
let props2 = props_opt2.unwrap();
10991135
assert!(props2.idle_timeout_seconds() == 1800);
11001136
assert!(props2.generated_passphrases_words_count() == 5);
1137+
1138+
let toml3 = r#"
1139+
version = "0.0.0"
1140+
"#;
1141+
let value3 = toml3.parse::<toml::value::Value>().unwrap();
1142+
let table3 = value3.as_table().unwrap();
1143+
let props_opt3 = super::Props::from_table(&table3);
1144+
assert!(props_opt3.is_ok());
1145+
let props3 = props_opt3.unwrap();
1146+
assert!(props3.idle_timeout_seconds() == 1800);
1147+
assert!(props3.generated_passphrases_words_count() == 5);
1148+
assert!(props3.version() == "0.0.0");
11011149
}
11021150

11031151
#[test]
@@ -1125,6 +1173,7 @@ mod api_unit_tests {
11251173
let toml = r#"
11261174
idle_timeout_seconds = 33
11271175
generated_passphrases_words_count = 5
1176+
version = "0.0.0"
11281177
"#;
11291178

11301179
let value = toml.parse::<toml::value::Value>().unwrap();
@@ -1136,6 +1185,29 @@ mod api_unit_tests {
11361185
assert!(table == &new_table);
11371186
}
11381187

1188+
#[test]
1189+
fn props_mutate() {
1190+
let toml = r#"
1191+
idle_timeout_seconds = 3
1192+
generated_passphrases_words_count = 3
1193+
version = "0.0.0"
1194+
"#;
1195+
1196+
let value = toml.parse::<toml::value::Value>().unwrap();
1197+
let table = value.as_table().unwrap();
1198+
let props_opt = super::Props::from_table(&table);
1199+
assert!(props_opt.is_ok());
1200+
let mut props = props_opt.unwrap();
1201+
1202+
props.set_idle_timeout_seconds(33);
1203+
props.set_generated_passphrases_words_count(33);
1204+
props.set_version("0.0.1");
1205+
1206+
assert!(props.idle_timeout_seconds() == 33);
1207+
assert!(props.generated_passphrases_words_count() == 33);
1208+
assert!(props.version() == "0.0.1");
1209+
}
1210+
11391211
#[test]
11401212
fn user_option_constructors() {
11411213
let opt1 = super::UserOption::cancel();

src/datacrypt.rs

Lines changed: 17 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@
1818
use std::cmp::PartialEq;
1919
use std::fmt::Debug;
2020
use std::iter::repeat;
21-
use std::thread;
22-
use std::thread::JoinHandle;
23-
2421
use aes::Aes256;
2522
use base64::{Engine as _, engine::general_purpose};
2623
use bcrypt::bcrypt;
@@ -34,9 +31,7 @@ use zeroize::Zeroize;
3431
use super::errors::{self, RustKeylockError};
3532
use super::protected::RklSecret;
3633

37-
const NUMBER_OF_SALT_KEY_PAIRS: usize = 10;
3834
type AesCtr = ctr::Ctr64BE<Aes256>;
39-
pub(crate) const BCRYPT_COST: u32 = 7;
4035

4136
pub trait Cryptor {
4237
/// Decrypts a given array of bytes
@@ -58,11 +53,8 @@ pub struct BcryptAes {
5853
iv: Vec<u8>,
5954
/// The position of the salt inside the file
6055
salt_position: usize,
61-
/// A list of pairs of salt - bcrypt key.
62-
///
63-
/// Each encryption process includes the creation of a new pseudo-random iv and the usage of one of the provided salt-key pairs.
64-
/// With these, the data is encrypted and the encrypted bytes are returned.
65-
salt_key_pairs: Vec<(Vec<u8>, RklSecret)>,
56+
/// The salt
57+
salt: Vec<u8>,
6658
/// A Hasher to be used to guarantee data integrity
6759
hasher: Sha3Keccak512,
6860
/// The hash that is retrieved by parsing the passwords file, during the application startup.
@@ -95,53 +87,31 @@ impl BcryptAes {
9587
/// * The position of the salt
9688
/// * hash for Sha3Keccak512 hashing
9789
pub fn new(mut password: String,
98-
mut salt: Vec<u8>,
90+
salt: Vec<u8>,
9991
iv: Vec<u8>,
10092
mut salt_position: usize,
101-
hash_bytes: Vec<u8>, )
102-
-> BcryptAes {
103-
let mut salt_key_pairs = Vec::new();
104-
let handles: Vec<JoinHandle<(Vec<u8>, RklSecret)>> = (0..NUMBER_OF_SALT_KEY_PAIRS + 1)
105-
.map(|i| {
106-
let cp = password.clone();
107-
let cs = salt.clone();
108-
let child = thread::spawn(move || {
109-
if i == 0 {
110-
// Create bcrypt password for the current encrypted data
111-
// Ask for 64 bytes bcrypt key. Use 32 bytes for data encryption and 32 bytes for hash encryption.
112-
let key = BcryptAes::create_key(cp.as_bytes(), &cs, BCRYPT_COST, 64);
113-
(cs, RklSecret::new(key))
114-
} else {
115-
// Create some new salt-key pairs to use them for encryption
116-
// Ask for 64 bytes bcrypt key. Use 32 bytes for data encryption and 32 bytes for hash encryption.
117-
let s = create_random(16);
118-
let k = BcryptAes::create_key(cp.as_bytes(), &s, BCRYPT_COST, 64);
119-
(s, RklSecret::new(k))
120-
}
121-
});
122-
child
123-
})
124-
.collect();
125-
for handle in handles {
126-
salt_key_pairs.push(handle.join().unwrap());
127-
}
93+
hash_bytes: Vec<u8>,
94+
bcrypt_cost: u32,
95+
) -> BcryptAes {
12896

12997
// Create the SHA3 hasher
13098
let hasher = Sha3Keccak512::new();
13199

100+
// Create bcrypt password for the current encrypted data
101+
// Ask for 64 bytes bcrypt key. Use 32 bytes for data encryption and 32 bytes for hash encryption.
102+
let key = BcryptAes::create_key(password.as_bytes(), &salt, bcrypt_cost, 64);
132103

133104
let to_ret = BcryptAes {
134-
key: salt_key_pairs.remove(0).1.clone(),
105+
key: RklSecret::new(key),
135106
iv,
136107
salt_position,
137-
salt_key_pairs,
108+
salt,
138109
hasher,
139110
hash: RklSecret::new(hash_bytes),
140111
};
141112

142113
// Zeroize what's not moved
143114
password.zeroize();
144-
salt.zeroize();
145115
salt_position.zeroize();
146116

147117
to_ret
@@ -172,19 +142,7 @@ impl Cryptor for BcryptAes {
172142
fn decrypt(&self, input: &[u8]) -> Result<Vec<u8>, RustKeylockError> {
173143
let bytes_to_decrypt = extract_bytes_to_decrypt(input, self.salt_position);
174144

175-
// The key should be 64 bytes long (including 2 32-byte keys). If it is bigger than that, there is the legacy key in the start.
176-
let legacy_handling = self.key.borrow().len() > 64;
177-
178-
let (final_result, integrity_check_ok) = if legacy_handling {
179-
let key: Vec<u8> = self.key.borrow().iter()
180-
.take(32)
181-
.cloned()
182-
.collect();
183-
let integrity_check_ok = self.hasher.validate_hash(&bytes_to_decrypt, self.hash.borrow());
184-
let final_result = self.decrypt_bytes(&bytes_to_decrypt, &key)?;
185-
186-
(final_result, integrity_check_ok)
187-
} else {
145+
let (final_result, integrity_check_ok) = {
188146
// The first 32 bytes of the key is for hash decryption.
189147
let hash_decryption_key: Vec<u8> = self.key.borrow().iter()
190148
.take(32)
@@ -216,20 +174,14 @@ impl Cryptor for BcryptAes {
216174
fn encrypt(&self, input: &[u8]) -> Result<Vec<u8>, RustKeylockError> {
217175
// Create a new iv
218176
let iv = create_random(16);
219-
let mut rng = thread_rng();
220-
// Choose randomly one of the salt-key pairs
221-
let idx = {
222-
rng.gen_range(0.. NUMBER_OF_SALT_KEY_PAIRS)
223-
};
224-
let salt_key_pair = &self.salt_key_pairs[idx];
225177

226178
// The first 32 bytes is the key for hash encryption.
227-
let hash_encryption_key: Vec<u8> = salt_key_pair.1.borrow().iter()
179+
let hash_encryption_key: Vec<u8> = self.key.borrow().iter()
228180
.take(32)
229181
.cloned()
230182
.collect();
231183
// The second 32 bytes is the key for data encryption.
232-
let data_encryption_key: Vec<u8> = salt_key_pair.1.borrow().iter()
184+
let data_encryption_key: Vec<u8> = self.key.borrow().iter()
233185
.skip(32)
234186
.take(32)
235187
.cloned()
@@ -242,7 +194,7 @@ impl Cryptor for BcryptAes {
242194
let encrypted_hash_bytes = self.encrypt_bytes(&hash_bytes, &hash_encryption_key, &iv)?;
243195

244196
// Compose the encrypted bytes with the iv and salt
245-
Ok(compose_bytes_to_save(&encrypted_data_bytes, self.salt_position, &salt_key_pair.0, &iv, &encrypted_hash_bytes))
197+
Ok(compose_bytes_to_save(&encrypted_data_bytes, self.salt_position, &self.salt, &iv, &encrypted_hash_bytes))
246198
}
247199
}
248200

@@ -480,6 +432,7 @@ fn compose_bytes_to_save(data: &[u8], salt_position: usize, salt: &[u8], iv: &[u
480432
#[cfg(test)]
481433
mod test_crypt {
482434
use super::{Cryptor, Hasher};
435+
const BCRYPT_COST: u32 = 1;
483436

484437
#[test]
485438
fn create_random() {
@@ -916,7 +869,7 @@ mod test_crypt {
916869
bytes.append(&mut tmp);
917870

918871
// Create the cryptor
919-
let cryptor = super::BcryptAes::new("password".to_string(), salt, iv, 1, hash);
872+
let cryptor = super::BcryptAes::new("password".to_string(), salt, iv, 1, hash, BCRYPT_COST);
920873
let result = cryptor.decrypt(&bytes);
921874
assert!(result.is_err());
922875
match result.err() {
@@ -925,19 +878,6 @@ mod test_crypt {
925878
}
926879
}
927880

928-
#[test]
929-
fn new_bcryptor_checks() {
930-
let iv = super::create_random(16);
931-
let salt = super::create_random(16);
932-
let hash = super::create_random(64);
933-
let cryptor = super::BcryptAes::new("password".to_string(), salt.clone(), iv, 1, hash);
934-
assert!(cryptor.salt_key_pairs.len() == super::NUMBER_OF_SALT_KEY_PAIRS);
935-
for skp in cryptor.salt_key_pairs {
936-
assert!(&skp.0 != &salt);
937-
assert!(&skp.1 != &cryptor.key);
938-
}
939-
}
940-
941881
#[test]
942882
fn bcrypt_key_creation() {
943883
let small_key = super::BcryptAes::create_key("123".as_bytes(), "saltsaltsaltsalt".as_bytes(), 3, 12);

0 commit comments

Comments
 (0)