Last active
October 26, 2018 05:17
-
-
Save petertseng/04efe23b3a176799e1101792f91006d0 to your computer and use it in GitHub Desktop.
lc4 and ls47
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
VERBOSE = ARGV.delete('-v') | |
def lc4(key) | |
keyed_cipher('#_23456789abcdefghijklmnopqrstuvwxyz', key, marker_moves: true) | |
end | |
def ls47(key) | |
keyed_cipher("_abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()", key, marker_moves: false) | |
end | |
def keyed_cipher(letters, key, marker_moves:) | |
size = letters.size | |
sqrt = (size ** 0.5).round | |
raise "Size #{size} not a square" if sqrt * sqrt != size | |
atoi = letters.each_char.with_index.to_h | |
itoa = letters.freeze | |
show_s = ->(s) { | |
s.map { |r| r.map { |c| itoa[c] }.join }.join(' ') | |
} | |
initial_s = Array.new(sqrt) { |row| | |
Array.new(sqrt) { |col| | |
atoi[key[row * sqrt + col]] | |
}.freeze | |
}.freeze | |
basic_operation = ->(s, input, marker_y, marker_x, polarity) { | |
s = s.map(&:dup) | |
output = input.each_char.map { |inc| | |
# A common substitution. | |
inc = ?_ if inc == ' ' && polarity > 0 | |
inci = atoi[inc] | |
raise "Improper character #{inc}" unless inci | |
iny = s.index { |rr| rr.include?(inci) } | |
inx = s[iny].index(inci) | |
sij = s[marker_y][marker_x] | |
outy = (iny + (sij / sqrt) * polarity) % sqrt | |
outx = (inx + sij * polarity) % sqrt | |
if polarity > 0 | |
cipher_y = outy | |
cipher_x = outx | |
plain_y = iny | |
plain_x = inx | |
else | |
cipher_y = iny | |
cipher_x = inx | |
plain_y = outy | |
plain_x = outx | |
end | |
itoa[(s[outy][outx]).tap { |outci| | |
# right rotate | |
s[plain_y].rotate!(-1) | |
cipher_x = (cipher_x + 1) % sqrt if cipher_y == plain_y | |
marker_x = (marker_x + 1) % sqrt if marker_y == plain_y && marker_moves | |
# down rotate | |
saved = s[-1][cipher_x] | |
s.each_index { |rr| | |
next unless (up = s[-2 - rr]) | |
s[-1 - rr][cipher_x] = up[cipher_x] | |
} | |
s[0][cipher_x] = saved | |
marker_y = (marker_y + 1) % sqrt if marker_x == cipher_x && marker_moves | |
moveci = polarity > 0 ? outci : inci | |
marker_y = (marker_y + moveci / sqrt) % sqrt | |
marker_x = (marker_x + moveci) % sqrt | |
}].tap { |outc| | |
puts "#{show_s[s]} #{marker_y} #{marker_x} #{inc} #{outc}" if VERBOSE | |
} | |
}.join | |
{ | |
s: s, | |
marker_y: marker_y, | |
marker_x: marker_x, | |
output: output, | |
} | |
} | |
basic_encrypt = ->(s, p, marker_y, marker_x) { | |
basic_operation[s, p, marker_y, marker_x, 1] | |
} | |
basic_decrypt = ->(s, c, marker_y, marker_x) { | |
basic_operation[s, c, marker_y, marker_x, -1] | |
} | |
{ | |
encrypt: ->(nonce, header, message, signature) { | |
s = initial_s.map(&:dup) | |
marker = [0, 0] | |
s, *marker = basic_encrypt[s, nonce + header, *marker].values_at(:s, :marker_y, :marker_x) | |
basic_encrypt[s, message + signature, *marker][:output] | |
}, | |
decrypt: ->(nonce, header, message, expected_signature) { | |
s = initial_s.map(&:dup) | |
marker = [0, 0] | |
s, *marker = basic_encrypt[s, nonce + header, *marker].values_at(:s, :marker_y, :marker_x) | |
{ | |
plaintext: m = basic_decrypt[s, message, *marker][:output], | |
sig_good: m.end_with?(expected_signature), | |
} | |
} | |
} | |
end | |
def assert_eq(want, got, desc) | |
raise "#{desc}: Want #{want}, got #{got}" if got != want | |
end | |
lc4_1 = lc4('xv7ydq#opaj_39rzut8b45wcsgehmiknf26l') | |
assert_eq( | |
'i2zqpilr2yqgptltrzx2_9fzlmbo3y8_9pyssx8nf2', | |
c = lc4_1[:encrypt][n = 'solwbf', '', p = 'im_about_to_put_the_hammer_down', s = '#rubberduck'], | |
'example encipher', | |
) | |
assert_eq({plaintext: p + s, sig_good: true}, lc4_1[:decrypt][n, '', c, s], 'example decipher') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment