KISA SEED Encryption Typescript로 포팅하기

predict-woo·2024년 3월 4일

학교 시스템 구현을 뒤져보던 중, 교내의 다양한 앱 및 서비스에서 KISA_SEED_CBC라는 흥미로운 함수를 암호화에 사용한다는 것을 알게 되었다. 이를 웹서비스로 구현할 필요성을 느끼고 있었기에, Typescript로의 포팅이 필요했다.

존재하는 솔루션

npm
일단 npm에는 없다

https://github.com/tomyun/crypto-js

아래에 js버전으로 구현된게 있지만, 이미 deprecated된 crypto-js 기반이고, typing이 되어있지 않다.

해결방안

https://seed.kisa.or.kr/kisa/algorithm/EgovSeedInfo.do
위 SEED 알고리즘 공식 문서를 살펴보면, Java 및 다양한 언어로 구현한 예제들을 볼 수 있다. 그중 내가 가장 잘 읽는 언어는 Java이기에, 이를 Ts로 포팅하기로 결정했다.

포팅

이번에 Java를 Typescript로 포팅하면서 느낀건데, 둘이 정말로 semantic이 비슷하다. 어느정도로 비슷하냐면, Java 코드를 복붙해서 가져온다음 타입 위치만 바꿔도 작동한다. 이 덕분에 빠른 속도로 변환이 가능했다.

스크린샷

타입변환

Java와 Typescript는 타입이 호환되지 않는다. 이 함수를 변환하면서 겪은 가장 큰 문제는 byte 타입에 관한 것이였다.

Java의 byte타입은 하나의 바이트, 즉 8비트를 나타낸다. 하지만, ts에는 숫자를 표현할 방법이 number 타입밖에 없다. 하지만 그렇다고 그냥 number 타입으로 통일해버리면 표현할 수 있는 수의 범위가 달라져버린다.
Java의 byteunsinged의 형태로 표현할 수 있는 숫자의 범위는 0~255인 반면, ts는 64 bit floating point의 최댓값인 1.7976931348623157e+308까지 표현할 수 있다.
따라서, Java에서 사용한 byte 변수를 사용할 때마다 variable & 0xff 의 형식으로 사이즈를 제한할 수밖에 없다.

다만, Java의 byte[] 는 ts의 Uint8Array로 포팅이 가능하기에, 이를 사용했다.

이후에도 Java의 int에 관련해서도 문제를 겪었다. Java에서 변수를 int로 정의하면, 정수만을 나타내기에 integer / 4 같은 연산은 자동으로 내림을 해주게 된다. 하지만, ts에서 같은 연산을 하게되면 float가 나오게 된다. 따라서, 매번 Math.floor(integer / 4) 와 같은 연산을 추가해주어야 한다.

클래스 변환

Typescript와 Java의 클래스는 놀랍도록 비슷하다. 거의 복붙 후에 자잘한 편집이 다였고, 이 프로젝트 덕분에 ts에서도 클래스 변수에 readonly를 사용할 수 있단걸 배우게 되었다.

편의성 추가

기존 클래스는 매번

  public static SEED_CBC_Encrypt(
    pbszUserKey: Uint8Array,
    pbszIV: Uint8Array,
    message: Uint8Array,
    message_offset: number,
    message_length: number
  ): Uint8Array

다음과 같은 인풋을 주어야 했기에, 다음과 같은 간단히 base64의 형식으로 인풋을 줄 수 있는 함수도 만들었다.

  public static encrypt(
    pbszUserKey: string,
    pbszIV: string,
    message_str: string
  ): string

커맨트

솔직히 말하자면, KISA에서 제공한 Java코드는 정말로 질이 안좋다. OOP 언어를 static variable들과 static method로 채워두고 functional하게 쓰고 있는건 기본이고,

private static final int KC0 = 0x9e3779b9;
private static final int KC1 = 0x3c6ef373;
private static final int KC2 = 0x78dde6e6;
private static final int KC3 = 0xf1bbcdcc;
private static final int KC4 = 0xe3779b99;
private static final int KC5 = 0xc6ef3733;
private static final int KC6 = 0x8dde6e67;
private static final int KC7 = 0x1bbcdccf;
private static final int KC8 = 0x3779b99e;
private static final int KC9 = 0x6ef3733c;
private static final int KC10 = 0xdde6e678;
private static final int KC11 = 0xbbcdccf1;
private static final int KC12 = 0x779b99e3;
private static final int KC13 = 0xef3733c6;
private static final int KC14 = 0xde6e678d;
private static final int KC15 = 0xbcdccf1b;

위와 같은 엄청난 변수정의도 있고,

private static final byte GetB0(int A) { return (byte)(A & 0x0ff); }
private static final byte GetB1(int A) { return (byte)((A>>8) & 0x0ff); }
private static final byte GetB2(int A) { return (byte)((A>>16) & 0x0ff); }
private static final byte GetB3(int A) { return (byte)((A>>24) & 0x0ff); }

위와 같은 신기한 매서드 정의도 있다.

최대한 많이 고치기는 했지만, 거의 2000줄이 되는 코드가 한 파일에 다 들어가 있어서 깔끔하게 만들지는 못한 것 같다. 그래도 무려 700줄가량의 코드로 줄이는데 성공했다!
또한 나의 혼동을 방지하기 위해 변수명이나 클래스 이름 같은 경우 best practice를 전혀 따르지 못하고 그대로 가지고 왔다.

결론

변환후에 npm package로 만들어 배포하였다. 사용 방법은 간단하고 dependency가 하나도 없으니 언제든지 사용해도 된다.
npm

profile
학생 개발자

1개의 댓글

comment-user-thumbnail
2025년 1월 8일

키야 잘쓰겠습니다

답글 달기