Skip to content

Commit 4a77b3d

Browse files
committed
new file: Encryption/age-lf.sf
new file: Encryption/insecurity_of_XOR.sf
1 parent 3b028c4 commit 4a77b3d

File tree

3 files changed

+297
-0
lines changed

3 files changed

+297
-0
lines changed

Encryption/age-lf.sf

+274
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
#!/usr/bin/ruby
2+
3+
# Author: Trizen
4+
# Date: 02 February 2022
5+
# Edit: 17 February 2022
6+
# https://github.com/trizen
7+
8+
# A large file encryption tool, inspired by Age, using Curve25519 and CBC+Serpent for encrypting data.
9+
10+
# See also:
11+
# https://github.com/FiloSottile/age
12+
# https://metacpan.org/pod/Crypt::CBC
13+
# https://metacpan.org/pod/Crypt::PK::X25519
14+
15+
# This is a simplified version of `plage`, optimized for large files:
16+
# https://github.com/trizen/perl-scripts/blob/master/Encryption/plage.pl
17+
18+
require('Crypt::CBC')
19+
require('Crypt::PK::X25519')
20+
require('JSON::PP')
21+
22+
STDIN.binmode(:raw)
23+
STDOUT.binmode(:raw)
24+
25+
define {
26+
SHORT_APPNAME = "age-lf",
27+
BUFFER_SIZE = (1024 * 1024),
28+
EXPORT_KEY_BASE = 62,
29+
VERSION = '0.01',
30+
}
31+
32+
var :CONFIG = (
33+
cipher => 'Serpent',
34+
chain_mode => 'CBC',
35+
)
36+
37+
func create_cipher (
38+
pass,
39+
cipher = CONFIG{:cipher},
40+
chain_mode = CONFIG{:chain_mode}
41+
) {
42+
%O<Crypt::CBC>.new(
43+
'-pass' => $pass,
44+
'-cipher' => "Cipher::#{cipher}",
45+
'-chain_mode' => chain_mode.lc,
46+
'-pbkdf' => 'pbkdf2',
47+
)
48+
}
49+
50+
func x25519_from_public (hex_key) {
51+
%O<Crypt::PK::X25519>.new.import_key(
52+
Hash(
53+
curve => "x25519",
54+
pub => hex_key,
55+
)
56+
)
57+
}
58+
59+
func x25519_from_private (hex_key) {
60+
%O<Crypt::PK::X25519>.new.import_key(
61+
Hash(
62+
curve => "x25519",
63+
priv => hex_key,
64+
)
65+
)
66+
}
67+
68+
func x25519_random_key {
69+
while (1) {
70+
var key = %O<Crypt::PK::X25519>.new.generate_key
71+
var hash = key.key2hash
72+
73+
next if hash{:pub}.starts_with('0')
74+
next if hash{:priv}.starts_with('0')
75+
76+
next if hash{:pub}.ends_with('0')
77+
next if hash{:priv}.ends_with('0')
78+
79+
return key
80+
}
81+
}
82+
83+
func encrypt (fh, public_key) {
84+
85+
# Generate a random ephemeral key-pair.
86+
var random_ephem_key = x25519_random_key()
87+
88+
# Create a shared secret, using the random key and the reciever's public key
89+
var shared_secret = random_ephem_key.shared_secret(public_key)
90+
91+
var cipher = create_cipher(shared_secret)
92+
var ephem_pub = random_ephem_key.key2hash(){:pub}
93+
var dest_pub = public_key.key2hash(){:pub}
94+
95+
var :info = (
96+
dest => dest_pub,
97+
cipher => CONFIG{:cipher},
98+
chain_mode => CONFIG{:chain_mode},
99+
ephem_pub => ephem_pub,
100+
)
101+
102+
var json = %S<JSON::PP>.encode_json(info)
103+
STDOUT.syswrite(pack("N*", json.len))
104+
STDOUT.syswrite(json)
105+
106+
cipher.start('encrypting')
107+
108+
while (fh.sysread(\(var buffer), BUFFER_SIZE)) {
109+
STDOUT.syswrite(cipher.crypt(buffer) \\ '')
110+
}
111+
112+
STDOUT.syswrite(cipher.finish)
113+
}
114+
115+
func decrypt (fh, private_key) {
116+
117+
if (!defined(private_key)) {
118+
die "No private key provided!\n"
119+
}
120+
121+
fh.sysread(\(var json_length), 32 >> 3)
122+
fh.sysread(\(var json), unpack("N*", json_length))
123+
124+
var enc = %S<JSON::PP>.decode_json(json)
125+
126+
# Make sure the private key is correct
127+
if (enc{:dest} != private_key.key2hash(){:pub}) {
128+
die "Incorrect private key!\n"
129+
}
130+
131+
# The ephemeral public key
132+
var ephem_pub = enc{:ephem_pub}
133+
134+
# Import the public key
135+
var ephem_pub_key = x25519_from_public(ephem_pub);
136+
137+
# Recover the shared secret
138+
var shared_secret = private_key.shared_secret(ephem_pub_key)
139+
140+
# Create the cipher
141+
var cipher = create_cipher(shared_secret, enc{:cipher}, enc{:chain_mode})
142+
143+
cipher.start('decrypting')
144+
145+
while (fh.sysread(\(var buffer), BUFFER_SIZE)) {
146+
STDOUT.syswrite(cipher.crypt(buffer) \\ '')
147+
}
148+
149+
STDOUT.syswrite(cipher.finish)
150+
}
151+
152+
func export_key (x_public_key) {
153+
Num(x_public_key, 16).base(EXPORT_KEY_BASE)
154+
}
155+
156+
func decode_exported_key (public_key) {
157+
Num(public_key, EXPORT_KEY_BASE).as_hex
158+
}
159+
160+
func decode_public_key (key) {
161+
x25519_from_public(decode_exported_key(key))
162+
}
163+
164+
func decode_private_key (file) {
165+
166+
file = File(file)
167+
168+
if (!file.is_text) {
169+
die "Invalid key file!\n"
170+
}
171+
172+
var key = %S<JSON::PP>.decode_json(file.read(:utf8))
173+
x25519_from_private(decode_exported_key(key{:x_priv}))
174+
}
175+
176+
func generate_new_key {
177+
178+
var x25519_key = x25519_random_key()
179+
var x_key = x25519_key.key2hash
180+
181+
var x_public_key = x_key{:pub}
182+
var x_private_key = x_key{:priv}
183+
184+
var :info = (
185+
x_pub => export_key(x_public_key),
186+
x_priv => export_key(x_private_key),
187+
)
188+
189+
say %S<JSON::PP>.encode_json(info)
190+
STDERR.printf("Public key: %s\n", info{:x_pub})
191+
return 1;
192+
}
193+
194+
func help (exit_code) {
195+
196+
var chaining_modes = %w(cbc pcbc cfb ofb ctr).sort.map{.uc}
197+
198+
var valid_ciphers = %w(
199+
AES Anubis Twofish Camellia Serpent SAFERP
200+
).sort
201+
202+
print <<"EOT"
203+
usage: #{__MAIN__} [options] [<input] [>output]
204+
205+
Encryption and signing:
206+
207+
-g --generate-key : Generate a new key-pair
208+
-e --encrypt=key : Encrypt data with a given public key
209+
-d --decrypt=key : Decrypt data with a given private key file
210+
--cipher=s : Change the symmetric cipher (default: #{CONFIG{:cipher}})
211+
valid: #{valid_ciphers.join(' ')}
212+
--chain-mode=s : Change the chaining mode (default: #{CONFIG{:chain_mode}})
213+
valid: #{chaining_modes.join(' ')}
214+
215+
Examples:
216+
217+
# Generate a key-pair
218+
#{__MAIN__} -g > key.txt
219+
220+
# Encrypt a message for Alice
221+
#{__MAIN__} -e=RBZ17knALkL5N1AWYjAgBwZDpQpQmvLbuTphVAx7XQC < message.txt > message.enc
222+
223+
# Decrypt a received message
224+
#{__MAIN__} -d=key.txt < message.enc > message.txt
225+
EOT
226+
227+
Sys.exit(exit_code)
228+
}
229+
230+
func version {
231+
printf("%s %s\n", SHORT_APPNAME, VERSION);
232+
Sys.exit(0)
233+
}
234+
235+
ARGV.getopt!(
236+
'cipher=s' => \CONFIG{:cipher},
237+
'chain-mode|mode=s' => \CONFIG{:chain_mode},
238+
'g|generate-key!' => \CONFIG{:generate_key},
239+
'e|encrypt=s' => \CONFIG{:encrypt},
240+
'd|decrypt=s' => \CONFIG{:decrypt},
241+
'v|version' => version,
242+
'h|help' => func { help(0) },
243+
)
244+
245+
if (CONFIG{:generate_key}) {
246+
generate_new_key()
247+
Sys.exit(0)
248+
}
249+
250+
func get_input_fh {
251+
var fh = STDIN
252+
253+
if (ARGV and fh.is_on_tty) {
254+
File(ARGV[0]).sysopen(\(var file_fh), 0) ||
255+
die "Can't open file <<#{ARGV[0]}>> for reading: $!"
256+
return file_fh
257+
}
258+
259+
return fh
260+
}
261+
262+
if (defined(CONFIG{:encrypt})) {
263+
var x_pub = decode_public_key(CONFIG{:encrypt})
264+
encrypt(get_input_fh(), x_pub)
265+
Sys.exit(0)
266+
}
267+
268+
if (defined(CONFIG{:decrypt})) {
269+
var x_priv = decode_private_key(CONFIG{:decrypt})
270+
decrypt(get_input_fh(), x_priv)
271+
Sys.exit(0)
272+
}
273+
274+
help(1)

Encryption/insecurity_of_XOR.sf

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/ruby
2+
3+
# XOR can be insecure!
4+
5+
var key1 = "a"*64 # Alice key
6+
var key2 = "z"*64 # Bob key
7+
8+
var msg = "Hello world! How are you doing?"
9+
10+
var c1 = (msg ^ key1) # Alice encrypts the message with her private key and sends it to Bob
11+
var c2 = ( c1 ^ key2) # Bob encrypts the encrypted message with his private key and sends the message back to Alice
12+
var c3 = ( c2 ^ key1) # Alice removes her encryption layer and sends the message back to Bob
13+
14+
say (c3 ^ key2) # Bob can now remove his layer of encryption and read the message
15+
16+
# However, this is insecure!
17+
# Someone that intercepts the encrypted messages, can decrypt them!
18+
19+
say (c2 ^ c3) # Alice's private key
20+
say (c1 ^ c2) # Bob's private key
21+
say (c1 ^ c2 ^ c3) # The original message decrypted from the encrypted messages

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ A nice collection of day-to-day Sidef scripts.
2727
* [Run length encoding](./Encoding/run_length_encoding.sf)
2828
* [Substitution cipher](./Encoding/substitution_cipher.sf)
2929
* Encryption
30+
* [Age-lf](./Encryption/age-lf.sf)
31+
* [Insecurity of XOR](./Encryption/insecurity_of_XOR.sf)
3032
* [One-time pad](./Encryption/one-time_pad.sf)
3133
* File
3234
* [Fdf](./File/fdf.sf)

0 commit comments

Comments
 (0)