fix SDA encryption compatibility (#236)

fixes #233
This commit is contained in:
Carson McManus 2023-06-26 19:02:48 -04:00 committed by GitHub
parent da44f49c56
commit f8ae7d4e0e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 106 additions and 117 deletions

67
Cargo.lock generated
View file

@ -10,14 +10,13 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]] [[package]]
name = "aes" name = "aes"
version = "0.7.5" version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cipher", "cipher",
"cpufeatures", "cpufeatures",
"opaque-debug",
] ]
[[package]] [[package]]
@ -122,21 +121,14 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "block-modes"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e"
dependencies = [
"block-padding",
"cipher",
]
[[package]] [[package]]
name = "block-padding" name = "block-padding"
version = "0.2.1" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
@ -162,6 +154,15 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
[[package]]
name = "cbc"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
dependencies = [
"cipher",
]
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.73" version = "1.0.73"
@ -195,11 +196,12 @@ dependencies = [
[[package]] [[package]]
name = "cipher" name = "cipher"
version = "0.3.0" version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [ dependencies = [
"generic-array", "crypto-common",
"inout",
] ]
[[package]] [[package]]
@ -366,6 +368,16 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]] [[package]]
name = "cssparser" name = "cssparser"
version = "0.27.2" version = "0.27.2"
@ -842,6 +854,16 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "inout"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
"block-padding",
"generic-array",
]
[[package]] [[package]]
name = "instant" name = "instant"
version = "0.1.12" version = "0.1.12"
@ -1139,12 +1161,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d11de466f4a3006fe8a5e7ec84e93b79c70cb992ae0aa0eb631ad2df8abfe2" checksum = "44d11de466f4a3006fe8a5e7ec84e93b79c70cb992ae0aa0eb631ad2df8abfe2"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]] [[package]]
name = "option-ext" name = "option-ext"
version = "0.2.0" version = "0.2.0"
@ -2214,7 +2230,7 @@ dependencies = [
"aes", "aes",
"anyhow", "anyhow",
"base64", "base64",
"block-modes", "cbc",
"clap", "clap",
"clap_complete", "clap_complete",
"cookie 0.14.4", "cookie 0.14.4",
@ -2222,6 +2238,7 @@ dependencies = [
"dirs", "dirs",
"gethostname", "gethostname",
"hmac-sha1", "hmac-sha1",
"inout",
"lazy_static 1.4.0", "lazy_static 1.4.0",
"log", "log",
"phonenumber", "phonenumber",

View file

@ -50,9 +50,8 @@ lazy_static = "1.4.0"
uuid = { version = "0.8", features = ["v4"] } uuid = { version = "0.8", features = ["v4"] }
steamguard = { version = "^0.9.0", path = "./steamguard" } steamguard = { version = "^0.9.0", path = "./steamguard" }
dirs = "3.0.2" dirs = "3.0.2"
ring = "0.16.20" ring = { version = "0.16.20", features = ["std"] }
aes = "0.7.4" aes = "0.8.3"
block-modes = "0.8.1"
thiserror = "1.0.26" thiserror = "1.0.26"
crossterm = { version = "0.23.2", features = ["event-stream"] } crossterm = { version = "0.23.2", features = ["event-stream"] }
qrcode = { version = "0.12.0", optional = true } qrcode = { version = "0.12.0", optional = true }
@ -62,6 +61,8 @@ zeroize = "^1.4.3"
serde_path_to_error = "0.1.11" serde_path_to_error = "0.1.11"
update-informer = { version = "1.0.0", optional = true, default-features = false, features = ["github"] } update-informer = { version = "1.0.0", optional = true, default-features = false, features = ["github"] }
phonenumber = "0.3" phonenumber = "0.3"
cbc = { version = "0.1.2", features = ["std"] }
inout = { version = "0.1.3", features = ["std"] }
[dev-dependencies] [dev-dependencies]
tempdir = "0.3" tempdir = "0.3"

View file

@ -412,21 +412,11 @@ pub enum ManifestAccountLoadError {
Unknown(#[from] anyhow::Error), Unknown(#[from] anyhow::Error),
} }
impl From<block_modes::BlockModeError> for ManifestAccountLoadError {
fn from(error: block_modes::BlockModeError) -> Self {
Self::Unknown(anyhow::Error::from(error))
}
}
impl From<base64::DecodeError> for ManifestAccountLoadError { impl From<base64::DecodeError> for ManifestAccountLoadError {
fn from(error: base64::DecodeError) -> Self { fn from(error: base64::DecodeError) -> Self {
Self::Unknown(anyhow::Error::from(error)) Self::Unknown(anyhow::Error::from(error))
} }
} }
impl From<block_modes::InvalidKeyIvLength> for ManifestAccountLoadError {
fn from(error: block_modes::InvalidKeyIvLength) -> Self {
Self::Unknown(anyhow::Error::from(error))
}
}
impl From<std::io::Error> for ManifestAccountLoadError { impl From<std::io::Error> for ManifestAccountLoadError {
fn from(error: std::io::Error) -> Self { fn from(error: std::io::Error) -> Self {
Self::Unknown(anyhow::Error::from(error)) Self::Unknown(anyhow::Error::from(error))
@ -639,11 +629,10 @@ mod tests {
manifest: "src/fixtures/maFiles/manifest-v1/1-account/manifest.json", manifest: "src/fixtures/maFiles/manifest-v1/1-account/manifest.json",
passkey: None, passkey: None,
}, },
// FIXME: disabled because of #233 Test {
// Test { manifest: "src/fixtures/maFiles/manifest-v1/1-account-encrypted/manifest.json",
// manifest: "src/fixtures/maFiles/manifest-v1/1-account-encrypted/manifest.json", passkey: Some(SecretString::new("password".into())),
// passkey: Some(SecretString::new("password".into())), },
// },
Test { Test {
manifest: "src/fixtures/maFiles/manifest-v1/2-account/manifest.json", manifest: "src/fixtures/maFiles/manifest-v1/2-account/manifest.json",
passkey: None, passkey: None,

View file

@ -374,11 +374,10 @@ mod tests {
dir: "src/fixtures/maFiles/compat/1-account/", dir: "src/fixtures/maFiles/compat/1-account/",
passkey: None, passkey: None,
}, },
// FIXME: disabled because of #233 Test {
// Test { dir: "src/fixtures/maFiles/compat/1-account-encrypted/",
// manifest: "src/fixtures/maFiles/compat/1-account-encrypted/", passkey: Some(SecretString::new("password".into())),
// passkey: Some(SecretString::new("password".into())), },
// },
Test { Test {
dir: "src/fixtures/maFiles/compat/2-account/", dir: "src/fixtures/maFiles/compat/2-account/",
passkey: None, passkey: None,
@ -402,9 +401,7 @@ mod tests {
for file in std::fs::read_dir(case.dir)? { for file in std::fs::read_dir(case.dir)? {
let file = file?; let file = file?;
let path = file.path(); let path = file.path();
eprintln!("copying {:?}", path);
let dest = temp.path().join(path.file_name().unwrap()); let dest = temp.path().join(path.file_name().unwrap());
eprintln!("to {:?}", dest);
std::fs::copy(&path, dest)?; std::fs::copy(&path, dest)?;
} }

View file

@ -1,6 +1,6 @@
use aes::cipher::block_padding::Pkcs7;
use aes::cipher::{BlockDecryptMut, BlockEncryptMut, InvalidLength, KeyIvInit};
use aes::Aes256; use aes::Aes256;
use block_modes::block_padding::{NoPadding, Padding, Pkcs7};
use block_modes::{BlockMode, Cbc};
use ring::pbkdf2; use ring::pbkdf2;
use ring::rand::SecureRandom; use ring::rand::SecureRandom;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -78,38 +78,22 @@ impl LegacySdaCompatible {
} }
} }
type Aes256Cbc = Cbc<Aes256, NoPadding>;
impl EntryEncryptor for LegacySdaCompatible { impl EntryEncryptor for LegacySdaCompatible {
// ngl, this logic sucks ass. its kinda annoying that the logic is not completely symetric.
fn encrypt( fn encrypt(
passkey: &str, passkey: &str,
params: &EntryEncryptionParams, params: &EntryEncryptionParams,
plaintext: Vec<u8>, plaintext: Vec<u8>,
) -> anyhow::Result<Vec<u8>, EntryEncryptionError> { ) -> anyhow::Result<Vec<u8>, EntryEncryptionError> {
let key = Self::get_encryption_key(passkey, &params.salt)?; let key = Self::get_encryption_key(passkey, &params.salt)?;
let iv = base64::decode(&params.iv)?; let mut iv = [0u8; IV_LENGTH];
let cipher = Aes256Cbc::new_from_slices(&key, &iv)?; base64::decode_config_slice(&params.iv, base64::STANDARD, &mut iv)?;
let origsize = plaintext.len(); let cipher = cbc::Encryptor::<Aes256>::new_from_slices(&key, &iv)?;
let buffersize: usize = (origsize / 16 + (if origsize % 16 == 0 { 0 } else { 1 })) * 16;
let mut buffer = vec![]; let ciphertext = cipher.encrypt_padded_vec_mut::<Pkcs7>(&plaintext);
for chunk in plaintext.as_slice().chunks(128) {
let chunksize = chunk.len(); let encoded = base64::encode(ciphertext);
let buffersize = (chunksize / 16 + (if chunksize % 16 == 0 { 0 } else { 1 })) * 16; Ok(encoded.as_bytes().to_vec())
let mut chunkbuffer = vec![0xffu8; buffersize];
chunkbuffer[..chunksize].copy_from_slice(chunk);
if buffersize != chunksize {
// pad the last chunk
chunkbuffer = Pkcs7::pad(&mut chunkbuffer, chunksize, buffersize)
.unwrap()
.to_vec();
}
buffer.append(&mut chunkbuffer);
}
let ciphertext = cipher.encrypt(&mut buffer, buffersize)?;
let final_buffer = base64::encode(ciphertext);
return Ok(final_buffer.as_bytes().to_vec());
} }
fn decrypt( fn decrypt(
@ -118,46 +102,51 @@ impl EntryEncryptor for LegacySdaCompatible {
ciphertext: Vec<u8>, ciphertext: Vec<u8>,
) -> anyhow::Result<Vec<u8>, EntryEncryptionError> { ) -> anyhow::Result<Vec<u8>, EntryEncryptionError> {
let key = Self::get_encryption_key(passkey, &params.salt)?; let key = Self::get_encryption_key(passkey, &params.salt)?;
let iv = base64::decode(&params.iv)?; let mut iv = [0u8; IV_LENGTH];
let cipher = Aes256Cbc::new_from_slices(&key, &iv)?; base64::decode_config_slice(&params.iv, base64::STANDARD, &mut iv)?;
let cipher = cbc::Decryptor::<Aes256>::new_from_slices(&key, &iv)?;
let decoded = base64::decode(ciphertext)?; let decoded = base64::decode(ciphertext)?;
let size: usize = decoded.len() / 16 + (if decoded.len() % 16 == 0 { 0 } else { 1 }); let size: usize = decoded.len() / 16 + (if decoded.len() % 16 == 0 { 0 } else { 1 });
let mut buffer = vec![0xffu8; 16 * size]; let mut buffer = vec![0xffu8; 16 * size];
buffer[..decoded.len()].copy_from_slice(&decoded); buffer[..decoded.len()].copy_from_slice(&decoded);
let decrypted = cipher.decrypt(&mut buffer)?; let decrypted = cipher.decrypt_padded_mut::<Pkcs7>(&mut buffer)?;
let unpadded = Pkcs7::unpad(decrypted)?; Ok(decrypted.to_vec())
Ok(unpadded.to_vec())
} }
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum EntryEncryptionError { pub enum EntryEncryptionError {
#[error("Invalid ciphertext length. The ciphertext must be a multiple of 16 bytes.")]
InvalidCipherTextLength,
#[error(transparent)] #[error(transparent)]
Unknown(#[from] anyhow::Error), Unknown(#[from] anyhow::Error),
} }
/// For some reason, these errors do not get converted to `ManifestAccountLoadError`s, even though they get converted into `anyhow::Error` just fine. I am too lazy to figure out why right now. /// For some reason, these errors do not get converted to `ManifestAccountLoadError`s, even though they get converted into `anyhow::Error` just fine. I am too lazy to figure out why right now.
impl From<block_modes::BlockModeError> for EntryEncryptionError { impl From<InvalidLength> for EntryEncryptionError {
fn from(error: block_modes::BlockModeError) -> Self { fn from(error: InvalidLength) -> Self {
Self::Unknown(anyhow::Error::from(error)) Self::Unknown(anyhow::Error::from(error))
} }
} }
impl From<block_modes::InvalidKeyIvLength> for EntryEncryptionError {
fn from(error: block_modes::InvalidKeyIvLength) -> Self { impl From<inout::NotEqualError> for EntryEncryptionError {
fn from(error: inout::NotEqualError) -> Self {
Self::Unknown(anyhow::Error::from(error)) Self::Unknown(anyhow::Error::from(error))
} }
} }
impl From<block_modes::block_padding::PadError> for EntryEncryptionError {
fn from(_error: block_modes::block_padding::PadError) -> Self { impl From<inout::PadError> for EntryEncryptionError {
Self::Unknown(anyhow!("PadError")) fn from(error: inout::PadError) -> Self {
Self::Unknown(anyhow::Error::from(error))
} }
} }
impl From<block_modes::block_padding::UnpadError> for EntryEncryptionError {
fn from(_error: block_modes::block_padding::UnpadError) -> Self { impl From<inout::block_padding::UnpadError> for EntryEncryptionError {
Self::Unknown(anyhow!("UnpadError")) fn from(error: inout::block_padding::UnpadError) -> Self {
Self::Unknown(anyhow::Error::from(error))
} }
} }
impl From<base64::DecodeError> for EntryEncryptionError { impl From<base64::DecodeError> for EntryEncryptionError {
fn from(error: base64::DecodeError) -> Self { fn from(error: base64::DecodeError) -> Self {
Self::Unknown(anyhow::Error::from(error)) Self::Unknown(anyhow::Error::from(error))
@ -178,14 +167,18 @@ mod tests {
#[test] #[test]
fn test_encryption_key() { fn test_encryption_key() {
assert_eq!( assert_eq!(
LegacySdaCompatible::get_encryption_key("password", "GMhL0N2hqXg=").unwrap(), LegacySdaCompatible::get_encryption_key("password", "GMhL0N2hqXg=")
.unwrap()
.as_slice(),
base64::decode("KtiRa4/OxW83MlB6URf+Z8rAGj7CBY+pDlwD/NuVo6Y=") base64::decode("KtiRa4/OxW83MlB6URf+Z8rAGj7CBY+pDlwD/NuVo6Y=")
.unwrap() .unwrap()
.as_slice() .as_slice()
); );
assert_eq!( assert_eq!(
LegacySdaCompatible::get_encryption_key("password", "wTzTE9A6aN8=").unwrap(), LegacySdaCompatible::get_encryption_key("password", "wTzTE9A6aN8=")
.unwrap()
.as_slice(),
base64::decode("Dqpej/3DqEat0roJaHmu3luYgDzRCUmzX94n4fqvWj8=") base64::decode("Dqpej/3DqEat0roJaHmu3luYgDzRCUmzX94n4fqvWj8=")
.unwrap() .unwrap()
.as_slice() .as_slice()
@ -194,12 +187,22 @@ mod tests {
#[test] #[test]
fn test_ensure_encryption_symmetric() -> anyhow::Result<()> { fn test_ensure_encryption_symmetric() -> anyhow::Result<()> {
let cases = [
"foo",
"tactical glizzy",
"glizzy gladiator",
"shadow wizard money gang",
"shadow wizard money gang, we love casting spells, shadow wizard money gang, we love casting spells, shadow wizard money gang, we love casting spells, shadow wizard money gang, we love casting spells, shadow wizard money gang, we love casting spells, shadow wizard money gang, we love casting spells, shadow wizard money gang, we love casting spells, shadow wizard money gang, we love casting spells, shadow wizard money gang, we love casting spells, shadow wizard money gang, we love casting spells, shadow wizard money gang, we love casting spells",
];
let passkey = "password"; let passkey = "password";
let params = EntryEncryptionParams::generate(); let params = EntryEncryptionParams::generate();
let orig = "tactical glizzy".as_bytes().to_vec(); for case in cases {
eprintln!("testing case: {} (len {})", case, case.len());
let orig = case.as_bytes().to_vec();
let encrypted = LegacySdaCompatible::encrypt(passkey, &params, orig.clone()).unwrap(); let encrypted = LegacySdaCompatible::encrypt(passkey, &params, orig.clone()).unwrap();
let result = LegacySdaCompatible::decrypt(passkey, &params, encrypted).unwrap(); let result = LegacySdaCompatible::decrypt(passkey, &params, encrypted).unwrap();
assert_eq!(orig, result.to_vec()); assert_eq!(orig, result.to_vec());
}
Ok(()) Ok(())
} }

View file

@ -1 +1 @@
Z0HJDSN9EuFOpKEeBzftCWxTsh0sV6QQriLTVrn37FyGNaXhGgzeHlvPfHgkXKCYbALTgx/B2fLh1CEojKO1/eqEgN+982CadR3EXk+vH1k5AMuGhMXPpsEeIh27ltxrdAEzWdlPlAyentBgOKlTCoN6iF+EZVORvp2pPaMrebyHi8/5Y+XC3HrMgfgmP7lFGpUgZK8f0mKB/pGaW+0/3oVikggBK3MIWlh4s9bC9LlMy5H+oU0n/Iu3P9dpbko1bDMKIbUKEPzS3wHXyQRg32zPIfONR0bswb7QTfAhoKixZrAenQluX3lXRL0JFafNEPzUY4r/DJ1pIMLK9cEvbzqwsPth6jrIZRd+zvgnshfNnGLCblYkPo4fGwePuhX2W2w6qgFMpo69rkSp1Zz6JKC/gH9YyL4a8N768ml9H1so5XBm7eB+fMRIL7bHof+V1CxGXX3z1RvjGRHPwKcrKLvffxTTs/dBHb9UDFtTprk= Z0HJDSN9EuFOpKEeBzftCWxTsh0sV6QQriLTVrn37FyGNaXhGgzeHlvPfHgkXKCYbALTgx/B2fLh1CEojKO1/eqEgN+982CadR3EXk+vH1k5AMuGhMXPpsEeIh27ltxrdAEzWdlPlAyentBgOKlTCoN6iF+EZVORvp2pPaMrebyHi8/5Y+XC3HrMgfgmP7lFGpUgZK8f0mKB/pGaW+0/3oVikggBK3MIWlh4s9bC9LlMy5H+oU0n/Iu3P9dpbko1bDMKIbUKEPzS3wHXyQRg32zPIfONR0bswb7QTfAhoKixZrAenQluX3lXRL0JFafNEPzUY4r/DJ1pIMLK9cEvbzqwsPth6jrIZRd+zvgnshfNnGLCblYkPo4fGwePuhX2W2w6qgFMpo69rkSp1Zz6JKC/gH9YyL4a8N768ml9H1so5XBm7eB+fMRIL7bHof+V1CxGXX3z1RvjGRHPwKcrKLvffxTTs/dBHb9UDFtTprlymLZf6C53c5vMBQ/hk4fm

View file

@ -1 +0,0 @@
ZQqJk4KW7pb5bndkra244z3ttIks58UplODn5IgljrOK3bKORSwrnZYb9Iv6YsirmrT0hQ3tx381GhQu4Nyj/PFHHCfFTrduaCoMLRrHDsmsexOW93Yo02acHXNfPSvxtjGfZpsuIZVlhVy8JDH/ESXp88cKn2zqjXQWu6pLnah1YPlpZuTfArw69+Em7V1OH1CoKnsYuhCo/x4u7fXhMNgDlRBvDbO4enGzaixonPu9er5Zp6iNEeuAUqmD0DHASygNmzBhUBHv8Avng8YKbvu611yQVT2KybnIoL+Q11Y36GoFhWskmG3lLwh/1OlReGwJ1iX0lDDthoel/Ygj6EC2+wkR8V7eMQf48R15xdBILYTzcrjtiuLCr9MBc/HM/ToEa3QCGwGkXvshR/meJ1BiqaRARKfvJcj4eMSpiUvhDe1QFXXjfXRdetJcknyJ8Pv6v10G/OV3ELYwdx2dYL5C+Ao4qj9QCjoD8bb/juCjtZoSxMncbm4T7ORbXs/Ulx+TEuOUmRAjxr+zaWzzO7ZfYJMhIPz+LSixKpaVmxP89DEK5LJ1T6jA50QmPft6AbOMuoq99haWH9lMgqrIfBB+ZNNHSEE9PwMUxhX/TQP3oJgmrnZcMV4nBOcovbWM5s+odu2mvzoAcRMHVxEztASbMBwdJ7amJvoRRrXTtp642FQHAe8pFPlW14x1hShXAO4YfYkmmAhLqlujammuQ6bRg7XjBvh0zE88i5UBRiRsH+MV35u3c0ciugUmbrAZ0u9Yfv19MNSMVNMsREDuxQ==

View file

@ -1,17 +0,0 @@
{
"encrypted": true,
"first_run": true,
"entries": [
{
"encryption_iv": "ifChnv66eA+/dYqGsQMIOA==",
"encryption_salt": "O2K8FAOWK9c=",
"filename": "1234.maFile",
"steamid": 1234
}
],
"periodic_checking": false,
"periodic_checking_interval": 5,
"periodic_checking_checkall": false,
"auto_confirm_market_transactions": false,
"auto_confirm_trades": false
}