Skip to content

Commit a613260

Browse files
authored
Merge pull request #55 from wiktor-k/wiktor-k/with-passwords
Allow using symmetric keys for encryption and decryption
2 parents 5233b87 + 0c45ee0 commit a613260

File tree

4 files changed

+109
-28
lines changed

4 files changed

+109
-28
lines changed

NEXT.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ v0.1.29
44

55
New:
66
- `Cert.generate` has a new `profile` parameter. The default is `Profile.RFC4880` which generates widely compatible certificates. The new option - `Profile.RFC9580` - generates newer, v6 certificates. Thanks to @jap for the contribution! [#47]
7+
- `encrypt` and `encrypt_file` can use symmetric encryption via the `passwords` argument
8+
- `decrypt` and `decrypt_file` can use symmetric encryption via the `passwords` argument
79

810
Changed:
911

README.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,16 @@ print(f"Encrypted data: {encrypted}")
202202

203203
The `signer` argument is optional and when omitted the function will return an unsigned (but encrypted) message.
204204

205+
Encryption to symmetric keys is available via the `passwords` optional argument:
206+
207+
```python
208+
from pysequoia import encrypt
209+
210+
content = "content to encrypt".encode("utf8")
211+
encrypted = encrypt(passwords = ["sekrit"], bytes = content).decode("utf8")
212+
print(f"Encrypted data: {encrypted}")
213+
```
214+
205215
### encrypt_file
206216

207217
Encrypts data from a file and writes the encrypted output to another file:
@@ -269,7 +279,7 @@ def get_certs(key_ids):
269279

270280
decrypted = decrypt(decryptor = receiver.secrets.decryptor("hunter22"), bytes = encrypted, store = get_certs)
271281

272-
assert content == decrypted.bytes.decode("utf8");
282+
assert content == decrypted.bytes.decode("utf8")
273283

274284
# let's check the valid signature's certificate and signing subkey fingerprints
275285
assert decrypted.valid_sigs[0].certificate == sender.fingerprint
@@ -278,6 +288,20 @@ assert decrypted.valid_sigs[0].signing_key == sender.fingerprint
278288

279289
Here, the same remarks as to [`verify`](#verify) also apply.
280290

291+
Decryption using symmetric keys is available via the `passwords` optional argument:
292+
293+
```python
294+
from pysequoia import encrypt
295+
296+
content = "content to encrypt"
297+
encrypted = encrypt(passwords = ["sekrit"], bytes = content.encode("utf8")).decode("utf8")
298+
print(f"Encrypted data: {encrypted}")
299+
decrypted = decrypt(passwords = ["sekrit"], bytes = encrypted.encode("utf-8"))
300+
print(f"Decrypted bytes: {decrypted.bytes}")
301+
302+
assert content == decrypted.bytes.decode("utf8")
303+
```
304+
281305
### decrypt_file
282306

283307
Decrypts data from a file and writes the decrypted output to another file:

src/decrypt.rs

Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::sync::{Arc, Mutex};
33

44
use anyhow::Context;
55
use pyo3::prelude::*;
6-
use sequoia_openpgp::crypto::{Decryptor, SessionKey};
6+
use sequoia_openpgp::crypto::{Decryptor, Password, SessionKey};
77
use sequoia_openpgp::packet::{PKESK, SKESK};
88
use sequoia_openpgp::parse::{stream::*, Parse};
99
use sequoia_openpgp::policy::StandardPolicy as P;
@@ -14,24 +14,30 @@ use crate::verify::PyVerifier;
1414
use crate::{Decrypted, ValidSig};
1515

1616
#[pyclass(from_py_object)]
17-
#[derive(Clone)]
17+
#[derive(Clone, Default)]
1818
pub struct PyDecryptor {
19-
inner: Arc<Mutex<Box<dyn Decryptor + Send + Sync + 'static>>>,
19+
inner: Option<Arc<Mutex<Box<dyn Decryptor + Send + Sync + 'static>>>>,
2020
verifier: Option<PyVerifier>,
21+
passwords: Vec<Password>,
2122
}
2223

2324
impl PyDecryptor {
2425
pub fn new(inner: Box<dyn Decryptor + Send + Sync + 'static>) -> Self {
2526
Self {
26-
inner: Arc::new(Mutex::new(inner)),
27+
inner: Some(Arc::new(Mutex::new(inner))),
2728
verifier: None,
29+
passwords: Vec::new(),
2830
}
2931
}
3032

3133
pub fn set_verifier(&mut self, verifier: impl Into<Option<PyVerifier>>) {
3234
self.verifier = verifier.into();
3335
}
3436

37+
pub fn set_passwords(&mut self, passwords: Vec<String>) {
38+
self.passwords = passwords.into_iter().map(Into::into).collect();
39+
}
40+
3541
pub fn valid_sigs(self) -> Vec<ValidSig> {
3642
if let Some(verifier) = self.verifier {
3743
verifier.valid_sigs()
@@ -42,15 +48,25 @@ impl PyDecryptor {
4248
}
4349

4450
#[pyfunction]
45-
#[pyo3(signature = (decryptor, bytes, store=None))]
51+
#[pyo3(signature = (bytes, decryptor=None, store=None, passwords=vec![]))]
4652
pub fn decrypt(
47-
mut decryptor: PyDecryptor,
4853
bytes: &[u8],
54+
decryptor: Option<PyDecryptor>,
4955
store: Option<Py<PyAny>>,
56+
passwords: Vec<String>,
5057
) -> PyResult<Decrypted> {
58+
if decryptor.is_none() && passwords.is_empty() {
59+
return Err(anyhow::anyhow!(
60+
"Either `decryptor` or `passwords` parameter should be given and non-empty."
61+
)
62+
.into());
63+
}
64+
let mut decryptor = decryptor.unwrap_or_default();
65+
decryptor.set_passwords(passwords);
5166
if let Some(store) = store {
5267
decryptor.set_verifier(PyVerifier::from_callback(store));
5368
}
69+
5470
let policy = &P::new();
5571

5672
let mut decryptor =
@@ -66,13 +82,22 @@ pub fn decrypt(
6682
}
6783

6884
#[pyfunction]
69-
#[pyo3(signature = (decryptor, input, output, store=None))]
85+
#[pyo3(signature = (input, output, decryptor=None, store=None, passwords=vec![]))]
7086
pub fn decrypt_file(
71-
mut decryptor: PyDecryptor,
7287
input: PathBuf,
7388
output: PathBuf,
89+
decryptor: Option<PyDecryptor>,
7490
store: Option<Py<PyAny>>,
91+
passwords: Vec<String>,
7592
) -> PyResult<Decrypted> {
93+
if decryptor.is_none() && passwords.is_empty() {
94+
return Err(anyhow::anyhow!(
95+
"Either `decryptor` or `passwords` parameter should be given and non-empty."
96+
)
97+
.into());
98+
}
99+
let mut decryptor = decryptor.unwrap_or_default();
100+
decryptor.set_passwords(passwords);
76101
if let Some(store) = store {
77102
decryptor.set_verifier(PyVerifier::from_callback(store));
78103
}
@@ -113,19 +138,31 @@ impl DecryptionHelper for PyDecryptor {
113138
fn decrypt(
114139
&mut self,
115140
pkesks: &[PKESK],
116-
_skesks: &[SKESK],
141+
skesks: &[SKESK],
117142
sym_algo: Option<SymmetricAlgorithm>,
118143
decrypt: &mut dyn FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool,
119144
) -> sequoia_openpgp::Result<Option<cert::Cert>> {
120-
let pair = &mut *self.inner.lock().unwrap();
121-
122-
for pkesk in pkesks.iter() {
123-
if pkesk
124-
.decrypt(pair, sym_algo)
125-
.map(|(algo, session_key)| decrypt(algo, &session_key))
126-
.is_some()
127-
{
128-
return Ok(None);
145+
for skesk in skesks.iter() {
146+
for password in self.passwords.iter() {
147+
if let Ok((algo, session_key)) = skesk.decrypt(password) {
148+
if decrypt(algo, &session_key) {
149+
return Ok(None);
150+
}
151+
}
152+
}
153+
}
154+
155+
if let Some(inner) = &mut self.inner {
156+
let pair = &mut *inner.lock().unwrap();
157+
158+
for pkesk in pkesks.iter() {
159+
if pkesk
160+
.decrypt(pair, sym_algo)
161+
.map(|(algo, session_key)| decrypt(algo, &session_key))
162+
.is_some()
163+
{
164+
return Ok(None);
165+
}
129166
}
130167
}
131168

src/encrypt.rs

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::borrow::Cow;
2+
use std::fs::File;
23
use std::io::Write;
34
use std::path::PathBuf;
45

@@ -69,13 +70,21 @@ fn resolve_recipient_keys(recipients: &[PyRef<Cert>]) -> PyResult<Vec<RecipientK
6970
}
7071

7172
#[pyfunction]
72-
#[pyo3(signature = (recipients, bytes, signer=None, *, armor=true))]
73+
#[pyo3(signature = (bytes, recipients=vec![], signer=None, passwords=vec![], *, armor=true))]
7374
pub fn encrypt(
74-
recipients: Vec<PyRef<Cert>>,
7575
bytes: &[u8],
76+
recipients: Vec<PyRef<Cert>>,
7677
signer: Option<PySigner>,
78+
passwords: Vec<String>,
7779
armor: bool,
7880
) -> PyResult<Cow<'static, [u8]>> {
81+
if recipients.is_empty() && passwords.is_empty() {
82+
return Err(anyhow::anyhow!(
83+
"Either `recipients` or `passwords` parameter should be given and non-empty."
84+
)
85+
.into());
86+
}
87+
7988
let recipient_keys = resolve_recipient_keys(&recipients)?;
8089

8190
let mut sink = vec![];
@@ -88,14 +97,15 @@ pub fn encrypt(
8897
message
8998
};
9099

91-
let mut message = Encryptor::for_recipients(
100+
let encryptor = Encryptor::for_recipients(
92101
message,
93102
recipient_keys
94103
.iter()
95104
.map(|(features, key)| Recipient::new(features.clone(), key.key_handle(), key)),
96105
)
97-
.build()
98-
.context("Failed to create encryptor")?;
106+
.add_passwords(passwords);
107+
108+
let mut message = encryptor.build().context("Failed to create encryptor")?;
99109

100110
if let Some(signer) = signer {
101111
message = Signer::new(message, signer)?.build()?;
@@ -112,17 +122,24 @@ pub fn encrypt(
112122
}
113123

114124
#[pyfunction]
115-
#[pyo3(signature = (recipients, input, output, signer=None, *, armor=true))]
125+
#[pyo3(signature = (input, output, recipients=vec![], signer=None, passwords=vec![], *, armor=true))]
116126
pub fn encrypt_file(
117-
recipients: Vec<PyRef<Cert>>,
118127
input: PathBuf,
119128
output: PathBuf,
129+
recipients: Vec<PyRef<Cert>>,
120130
signer: Option<PySigner>,
131+
passwords: Vec<String>,
121132
armor: bool,
122133
) -> PyResult<()> {
134+
if recipients.is_empty() && passwords.is_empty() {
135+
return Err(anyhow::anyhow!(
136+
"Either `recipients` or `passwords` parameter should be given and non-empty."
137+
)
138+
.into());
139+
}
123140
let recipient_keys = resolve_recipient_keys(&recipients)?;
124141

125-
let mut sink = std::fs::File::create(&output).context("Failed to create output file")?;
142+
let mut sink = File::create(&output).context("Failed to create output file")?;
126143

127144
let message = Message::new(&mut sink);
128145

@@ -138,6 +155,7 @@ pub fn encrypt_file(
138155
.iter()
139156
.map(|(features, key)| Recipient::new(features.clone(), key.key_handle(), key)),
140157
)
158+
.add_passwords(passwords)
141159
.build()
142160
.context("Failed to create encryptor")?;
143161

@@ -148,7 +166,7 @@ pub fn encrypt_file(
148166
.build()
149167
.context("Failed to create literal writer")?;
150168

151-
let mut input_file = std::fs::File::open(&input).context("Failed to open input file")?;
169+
let mut input_file = File::open(&input).context("Failed to open input file")?;
152170
std::io::copy(&mut input_file, &mut message)?;
153171

154172
message.finalize()?;

0 commit comments

Comments
 (0)