Skip to content

Instantly share code, notes, and snippets.

@petertseng
Last active October 26, 2018 05:17
Show Gist options
  • Save petertseng/04efe23b3a176799e1101792f91006d0 to your computer and use it in GitHub Desktop.
Save petertseng/04efe23b3a176799e1101792f91006d0 to your computer and use it in GitHub Desktop.
lc4 and ls47
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