본문 바로가기
카테고리 없음

암호화 프로그램 만들기2

by 경자꿈사 2024. 12. 23.

오늘은 지난 글에 이어서 치환 암호의 일종으로 문자들의 위치를 변경시키는 방법으로 암호화를 하는 프로그램을 만들어 보려고 한다.
길이가 5인 문자열 "abcde"가 있을 때 각 문자의 인덱스는 0부터 시작해서 1씩 증가하여 젤 마지막 문자의 인덱스는 4가 된다.
여기서 문자열 "abcde"의 모든 문자들을 오른쪽 방향으로 1만큼 이동시킨다고 해보자.
만약 비어 있는 자리를 공백으로 채운다면 이동 후의 문자열은 " abcde"가 될 것이다. 즉 문자열의 길이는 이동 크기만큼 늘어나게 될 것이고 각 문자들의 인덱스 역시 단순히 이동 크기만큼 증가될 것이다.
그런데 문자열의 길이를 변경시키지 않고 문자들을 이동시키려면 어떻게 해야 될까? 이때는 마지막 인덱스를 넘어설 경우 다시 인덱스 0부터 시작한다고 생각하면 된다.
쉽게 설명하면, 문자열 "abcde"에서 마지막 문자 'e'의 인덱스는 4이고 단순히 인덱스를 증가시킨다면 5, 6, 7, 8, 9...처럼 커지게 되는데 가장 큰 인덱스인 4보다 커질 경우 다시 0부터 시작한다는 얘기는 
5는 0, 6은 1, 7은 2, 8은 3 그리고 9는 4로 간주한다는 얘기다. 같은 방식으로 10은 다시 0으로 매핑 시켜야 한다.
즉 이동 크기만큼 증가시킨 인덱스를 다시 원래의 인덱스 범위 안의 값으로 변경시켜 주면 되는데, 나머지 연산을 통해 쉽게 해결할 수 있다.
5, 6, 7, 8, 9를 각각 문자열의 길이인 5로 나누면 나머지가 0, 1, 2, 3, 4가 되고 5의 배수인 10 역시 5로 나누면 나머지가 0이 된다.
생각해 보면 문자열의 인덱스는 어떤 수(0 이상의 정수)를 문자열의 길이에 해당하는 수로 나눴을 때 나올 수 있는 나머지 중 하나와 같은 값임을 알 수 있다.
아래 ShiftEncryptor 클래스의 코드에서도 이동 후 문자의 위치를 찾기 위해 나머지 연산을 사용하였다.
encrypt 메서드를 보면 대상 문자열과 이동 크기를 인수로 받는데, 만약 이동 크기가 문자열의 길이의 배수라면 실제 이동이 없는 것과 마찬가지이므로 그냥 복사본을 반환하고 종료한다.
그렇지 않고 실제 이동이 필요하다면 문자들을 하나씩 순회하면서 문자의 이동 후 인덱스를 계산하고 복사본에서 해당 인덱스의 문자를 변경한다.

class ShiftEncryptor
  def encrypt(str, shift)
    raise ArgumentError, "shift must be greater than or equal to 0" if shift < 0
    enc_str = str.dup
    return enc_str if shift % str.size == 0
    
    0.upto(str.size - 1) do |i|
      shift_idx = (i + shift) % str.size
      enc_str[shift_idx] = str[i]
    end  
    enc_str
  end

  def decrypt(str, shift)
    raise ArgumentError, "shift must be greater than or equal to 0" if shift < 0  
    shift = str.size - (shift % str.size)
    encrypt(str, shift)
  end

decrypt 메서드는 이동한 문자들을 원래의 위치로 되돌려야 하는데, 특정 위치의 문자를 문자열의 길이만큼 이동시키면 다시 제자리로 돌아오는 점을 이용하였다. 
코드에서처럼 나머지 연산을 통해 실제 이동한 크기를 구해(길이가 5인 문자열을 7만큼 이동시키는 것은 실제 2만큼 이동시키는 것과 같다.) 문자열의 길이에서 빼면 원래의 문자열로 되돌리기 위해 필요한 이동 크기가 나온다.
그리고 encrypt 메서드를 사용하여 해당 크기만큼 문자열을 이동시켜 암호화된 문자열을 복호화 시킨다.

이제 테스트를 위해 ShiftEncryptor 클래스의 코드를 encryption 폴더 아래 shift_encryptor.rb 파일에 저장하자.
그리고 같은 폴더의 위치에서 irb를 실행하여 아래 그림처럼 예제 코드를 입력해 보자.

앞의 예제에서 알파벳 26개로 구성된 문자열에 대해 이동 크기를 1부터 26까지 변경해가며 문자열을 암호화하고 있는데, 출력된 결과를 보면 특정 알파벳 문자가 오른쪽으로 한 칸씩 이동해 가는 것을 볼 수 있다.
ShiftEncryptor 클래스의 encrypt와 decrypt 메서드로 암호화 및 복호화가 잘 되긴 하지만, 이 프로그램을 그대로 사용하기에는 암호화 수준이 상당히 낮은 걸 알 수 있다.
대상 문자열을 같은 크기를 갖는 여러 개의 블록으로 나눈 후 각각의 블록 안에서 문자의 위치를 변경한다면 암호화 수준을 조금 더 높일 수 있을 것 같다.
이 아이디어를 반영하여 BlockShiftEncryptor 클래스의 코드를 아래처럼 작성해 보았다.
encrypt와 decrypt 메서드를 보면 step 메서드를 사용하여 대상 문자열의 인덱스를 블록 크기의 간격으로 순회하면서 블록 크기에 해당하는 서브 문자열을 암호화해 enc_str에 계속 이어 붙이고 있다.
실제 암호화 처리는 do_encrypt 메서드에서 하는데 앞서 만든 ShiftEncryptor 클래스의 encrypt 메서드와 동일한 로직이다.

class BlockShiftEncryptor
  def initialize(block_size, shift)
    raise ArgumentError, "block_size must be greater than or equal to 2" if block_size < 2
    raise ArgumentError, "shift must be greater than 0 and less than block_size" if shift < 1 || shift >= block_size
    
    @block_size = block_size
    @shift = shift
  end

  def encrypt(str)
    enc_str = ""
    
    0.step(str.size - 1, @block_size) do |i|
      block = str[i, @block_size]
      enc_str << do_encrypt(block, @shift)
    end  
    enc_str
  end

  def decrypt(str)
    dec_str = ""
    
    0.step(str.size - 1, @block_size) do |i|
      block = str[i, @block_size]    
      dec_str << do_encrypt(block, block.size - (@shift % block.size))
    end  
    dec_str    
  end
  
  private
  def do_encrypt(str, shift)
    enc_str = str.dup
    return enc_str if shift % str.size == 0
    
    0.upto(str.size - 1) do |i|
      shift_idx = (i + shift) % str.size
      enc_str[shift_idx] = str[i]
    end  
    enc_str
  end
end

이제 테스트를 위해 먼저 BlockShiftEncryptor 클래스의 코드를 encryption 폴더 아래 block_shift_encryptor.rb 파일에 저장하자.
그리고 같은 폴더의 위치에서 irb를 실행하여 다음 그림처럼 코드를 입력해 보자.

결과를 보면 ShiftEncryptor 클래스에 비해 BlockShiftEncryptor 클래스의 암호화 수준이 조금 더 높은 걸 알 수 있다.
동일한 문자열에 대해 블록의 크기와 문자 이동의 크기를 바꿔가며 여러 차례 암호화를 반복하면 그만큼 해독하기가 더 어려운 문자열을 얻을 수 있을 것이다.
그리고 이 코드를 기반으로 조금 더 암호화 수준이 높은 코드를 여러분이 직접 작성해 보면 좋을 것 같다.
다음 글에서는 XOR 비트 연산을 사용하여 암호화하는 프로그램을 만들어 보겠다.

See you again~~