전편에 이어 실제로 파일을 암호화/해독화 해보겠습니다.

OpenSSL 커맨드 라인 도구를 사용하면 프로그램을 따로 작성하지 않아도 암호화를 할 수 있습니다.

우선 원본 파일을 준비합니다.

$ cat original.txt
Hello World

openssl list-cipher-commands를 실행하면 사용가능한 알고리즘을 보여줍니다. 그중 AES 알고리즘은 다음과 같습니다.

$ openssl list-cipher-commands | grep aes
aes-128-cbc
aes-128-ecb
aes-192-cbc
aes-192-ecb
aes-256-cbc
aes-256-ecb

128, 192, 256은 키의 크기입니다. CBC, ECB는 블록 암호 운용 방식을 나타냅니다. 여기서는 aes-256-cbc로 암호화를 해보겠습니다.

$ openssl aes-256-cbc -in original.txt -out encrypted.dat
enter aes-256-cbc encryption password:
Verifying - enter aes-256-cbc encryption password:

해독화는 -d 옵션을 사용합니다.

$ openssl aes-256-cbc -d -in encrypted.dat -out restored.txt
enter aes-256-cbc decryption password:

암호화를 자동화하려면 암호를 명령에 포함시켜야 합니다. 이를 위해서는 -k 나 -pass 옵션을 사용합니다.

$ openssl aes-256-cbc -in original.txt -out encrypted.dat -k 1234

또는

$ openssl aes-256-cbc -in original.txt -out encrypted.dat -pass pass:1234

암호를 파일에서 읽도록 하려면 -kfile 이나 -pass 옵션을 사용합니다.

$ openssl aes-256-cbc -in original.txt -out encrypted.dat -kfile password_file

또는

$ openssl aes-256-cbc -in original.txt -out encrypted.dat -pass file:password_file

다음에는 암호화한 파일을 iOS나 Android에서 해독화하는 방법에 대해서 알아보겠습니다.

한때 하이브리드 앱이 유행했지만 성능문제로 어느 이상을 커지지는 못했습니다. 그러던 와중에 React Native가 나오면서 다시 하나의 코드로 여러 플랫폼을 지원하는 방식이 탄력을 받고 있습니다.

저도 React Native등을 써서 한번의 코딩으로 iOS와 안드로이드 모두를 지원하고 싶은 생각은 간절하지만, 같이 일하는 기획자가 그렇게 만들어진 앱의 느낌에 만족하지 못하는 고로, 더 많은 발전이 있기 전에는 React Native로 넘어가지 못할 것 같습니다.

그렇지만 현재 만들고 있는 앱을 네이티브(Swift, Java등)로만 작성하고 있는 것은 아닙니다. UI/UX에 상관이 없고 자주 갱신되어야 하는 부분은 JavaScript로 작성해서 앱 업데이트 없이도 갱신할 수 있도록 하고 있습니다.

그런데 이 스크립트가 나름 앱의 핵심 노하우라고 여기고 있기에 외부에서 보지 못하도록 하고 싶었습니다. 물론 난독화(uglify)만으로도 분석이 어려워지긴 합니다. 그래도 간단한 암호화라도 추가 하고 싶어서 조금 찾아봤습니다.

암호화 종류를 살펴보기에 앞서 짚어볼 가장 중요한 점은 ‘100% 안전한 방법은 없다’ 입니다. 당연한 것이 이 스크립트는 언젠가 JavaScript 엔진이 이해할 수 있는 형태로 변환되어야 하기 때문입니다. 다만 적어도 단순히 ipa, apk를 푸는 것만으로 스크립트를 가져가는 것은 방지하고 싶었습니다.


암호화는 일단 크게 대칭 방식과 비대칭 방식이 있습니다.

대칭 방식은 암호화화 해독화를 같은 키로 하는 것입니다. 속도가 빠른 대신 키를 양측이 모두 알고 있어야 하기에 키가 노출될 가능성이 높습니다. 주요 알고리즘으로 AES가 있습니다.

비대칭 방식은 암호화화 해독화를 다른 키로 하는 것입니다. 속도가 느린 대신 해독화 키(비밀 키)를 나만 가지고 있기 때문에 노출될 가능성이 적어집니다. 주요 알고리즘으로 RSA가 있습니다.

반면 보안에 관련된 얘기에서 종종 나오곤 하는 MD5, SHA, HMAC등은 해시를 만들어내는 것으로, 원문이 변형되지 않았는지(무결성) 검사하기 위함이지 그 자체로 원문을 복원하지는 못합니다. 다만 ‘암호가 MD5로 저장되어서 원래 암호를 얻어냈다’ 같은 뉴스가 있는 것은 원문(암호)의 크기가 작기 때문에 높은 확률로 원문이라고 짐작되는 글을 알아냈다라는 것이지, 원문을 복원했다는 것은 아닙니다.


처음에는 내 키가 노출되는게 꺼려져서 RSA를 알아봤습니다. 그런데 RSA는 암호화하려는 데이터가 키보다 작아야 한다는 것을 알게 됐습니다. 그렇기에 원문 암호화는 AES로 하지만 그 키는 RSA로 암호화하는 방식등을 사용하는 것 같습니다.

그래서 스크립트는 AES로 암호화하기로 했습니다. 암호화 키는 RSA로 암호화를 하려다가 키를 얻기 위해 앱을 해킹한다면 어떻게 하든 알아낼 수 있을 것으로 판단해서, 암호화 키는 문자열로 바로 노출되는 것만 막는 정도로 처리하기로 했습니다.

다음에는 실제로 AES로 암호화 하는 방법에 대해서 알아보겠습니다.

C
해당 기능이 존재하지 않음
C++
double logarithm(double x, double base=10) {
  return log(x) / log(base);
}
CoffeeScript
logarithm = (x, base=10) ->
  Math.log(x) / Math.log(base)
Java
double logarithm(double x) {
  return logarithm(x, 10);
}
double logarithm(double x, double base) {
  return Math.log(x) / Math.log(base);
}
JavaScript
function logarithm(x, base) {
  if (base==null) {
    base = 10;
  }
  return Math.log(x) / Math.log(base);
}
Kotlin
fun logarithm(x: Double, base: Double=10.0): Double {
  return Math.log(x) / Math.log(base)
}
Lua
function logarithm(x, base)
  base = base or 10
  return math.log(x) / math.log(base)
end}
Objective-C
해당 기능이 존재하지 않음
Perl
sub logarithm {
  my $x = shift;
  my $base = shift // 10;
  log($x) / log($base);
}
PHP
function logarithm($x, $base=10) {
  return log($x) / log($base);
}
Python
def logarithm(x, base=10):
  return math.log(x) / math.log(base)
Ruby
def logarithm(x, base=10)
  Math.log(x) / Math.log(base)
end
Swift
func logarithm(x: Double, base: Double=10) -> Double {
  return log(x) / log(base)
}