오늘은 '암호화 프로그램 만들기' 마지막 글로 XOR 비트 연산을 사용해서 암호화하는 프로그램을 만들어 보자.
컴퓨터에서 비트는 0과 1 두 가지 값을 갖는 가장 작은 데이터 단위로 논리 연산과 이동(Shift) 연산 등 할 수 있다.
XOR는 논리 연산 중 하나로 두 비트의 값이 서로 같으면 0 다르면 1이 되는 연산을 말한다.
아래 그림을 보면 10과 12에 대해 XOR 연산(^)을 하면 결괏값이 6이 나오는데, 십진수인 10과 12 그리고 6을 각각 이진수로 표현해서 살펴보면 왜 결괏값이 6이 나왔는지 알 수 있다.
1010과 1100에 대해 같은 위치의 비트들을 서로 비교해 보면 끝에서 두 번째와 세 번째 비트가 서로 달라 각각 1이 되고 첫 번째와 네 번째 비트는 같기 때문에 각각 0이 된다.
그래서 1010과 1100의 XOR 연산 결과는 0110이 되고 이것을 십진수로 표현하면 6이 된다.
아래 그림을 보면 십진수 숫자를 이진수 문자열로 변환하는 메서드와 이진수 문자열을 십진수 숫자로 변환하는 메서드를 직접 작성해서 테스트해 보았다.
이제 본격적으로 XOR 비트 연산을 사용해서 암호화하는 프로그램을 아래처럼 작성해 보자.
encrypt 메서드의 코드를 보면 대상 문자열을 바이트 단위로 인덱스와 함께 순회하면서 암호화 키의 바이트 배열에서 해당하는 바이트를 가져와 XOR 연산을 수행하고 그 결괏값을 enc_arr 배열에 하나씩 추가한다.
그리고 최종적으로 배열의 pack 메서드를 사용해서 바이트 배열을 문자열로 변환하여 반환한다.
decrypt 메서드는 복호화를 위해 단순히 encrypt 메서드를 호출하는데, XOR 연산의 특성상 XOR 연산의 결괏값에 다시 한번 동일한 값으로 XOR 연산을 수행하면 원래의 값이 나오기 때문이다.
class XorEncryptor
def encrypt(str, key)
key_bytes = key.bytes
enc_arr = []
str.each_byte.with_index do |b, i|
enc_arr << (b ^ key_bytes[i % key_bytes.size])
end
enc_arr.pack("C*")
end
def decrypt(str, key)
encrypt(str, key)
end
end
아래 그림을 보면 10과 12를 XOR 연산한 결괏값 6에 대해 다시 12와 XOR 연산을 하면 원래의 값 10이 나오는 걸 볼 수 있다.
어떤 비트 A와 1을 XOR 연산한 결괏값이 1이 나왔다면 비트 A는 0이라는 얘기이고 이 값은 결괏값 1에 다시 1을 XOR 하면 얻을 수 있고,
결괏값이 0이 나왔다면 비트 A는 1이라는 얘기이고 이 또한 결괏값 0에 다시 1을 XOR 하면 얻을 수 있다.
그리고 어떤 비트 A에 0을 XOR 하는 경우에도 마찬가지로 결괏값에 다시 0을 XOR 하면 비트 A의 값을 구할 수 있다.
이제 테스트를 위해 XorEncryptor 클래스의 코드를 encryption 폴더 아래 xor_encryptor.rb 파일에 저장하자.
그리고 같은 폴더의 위치에서 irb를 실행하여 다음 그림처럼 코드를 입력해 보자.
암호화와 복호화가 잘 되는 것을 볼 수 있다.
마지막으로 원하는 파일을 암호화하고 다시 복호화 할 수 있는 유틸 프로그램을 만들어보자.
아래 코드를 보면 BlockShiftEncryptor, ReplaceEncryptor, XorEncryptor 세 가지 클래스를 사용하여 암호화와 복호화를 하는 것을 볼 수 있는데,
중요한 것은 복호화 시에는 암호화를 할 때 적용했던 순서와 반대의 순서로 적용해야 한다는 것이다.
그리고 ReplaceEncryptor 클래스의 객체를 생성할 때 인수를 생략하면 랜덤한 방식을 적용하여 생성한 대치 문자 세트를 사용하기 때문에 복호화가 제대로 되지 않는다.
따라서 평문의 문자 세트와 대치할 문자 세트를 인수로 주고 ReplaceEncryptor 객체를 생성하였다.
파일의 내용을 암호화하고 복호화 하는 과정에서 파일의 원본 내용이 손상되지 않도록 바이너리 모드로 읽고(rb) 썼다(rw).
if ARGV.size != 3 || !["-e", "-d"].include?(ARGV[0]) || !File.file?(ARGV[1])
puts "[사용법]"
puts "암호화: ruby enc.rb -e 파일명 비밀번호"
puts "복호화: ruby enc.rb -d 파일명 비밀번호"
exit
end
require './block_shift_encryptor'
require './replace_encryptor'
require './xor_encryptor'
plain_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
rep_chars = "TpzFELHvmMBcCawAIKJdQGrljOxebNuVXykPStDnWoUYhsRfZigq"
blk_enc = BlockShiftEncryptor.new(5, 3)
rep_enc = ReplaceEncryptor.new(plain_chars, rep_chars)
xor_enc = XorEncryptor.new
mode, file, password = ARGV
if mode == "-e"
str = File.read(file, mode: "rb")
str = blk_enc.encrypt(str)
str = rep_enc.encrypt(str)
str = xor_enc.encrypt(str, password)
File.open(file + ".enc", "wb") { |f| f.write(str) }
elsif mode == "-d"
str = File.read(file, mode: "rb")
str = xor_enc.decrypt(str, password)
str = rep_enc.decrypt(str)
str = blk_enc.decrypt(str)
File.open(file + ".dec", "wb") { |f| f.write(str) }
end
위의 코드를 encryption 폴더 아래 enc.rb 파일에 저장한 후 아래 그림처럼 테스트를 진행해 보자.
text.txt 파일의 내용이 암호화되고 다시 정상적으로 복호화되는 것을 볼 수 있다.
See you again~~