지금으로부터 약 10년전 꼬꼬마 대학생 시절 RSA 알고리즘에 사용하는 소수 중 하나를 257로 고정하면 바이트를 그 알고리즘을 통해 나온 값은 반드시 0~256이 나온다고 글을 올린 적이 있었다.

[C#] RSA를 이용한 File Encryp.. : 네이버블로그

근데 이건 진짜 암호화라기 보다는 그냥 RSA 알고리즘을 이용해서 비슷한 짓을 한 것이었다. 사실 여기서 나오는 키(257이 아닌 또 하나의 소수)를 저장하는 것이나, 그게 소수여야 하기 때문에 고르는 것, 이런 저런 이슈가 많은 짓이었다.

양자 컴퓨터의 대두

그리고 10년쯤 지나 일하다 보니 고객들 파일을 저장할 때는 반드시 암호화 해야 하고, 이런저런 것을 해보니 대충 이런 거 쓰면 저걸 다시 만들 수 있겠다 싶었다. (참고로 비밀번호 저장과는 다르다. 비밀번호는 단방향 해시라서 원본을 알 수 없도록 해야 한다. 그러나 파일 암호화는 파일을 어쨌든 복호화 할 수 있어야 하기 때문이다.)

저 짓을 하고 10년이 지났는데 그동안 양자 컴퓨터 얘기도 많이 나오고 기존 RSA 알고리즘이 무너질 수 있다는 여러 이야기가 나왔는데, 일단 이것은 양자 컴퓨터는 기존 컴퓨터보다 더 빠르게 RSA에서 사용하는 엄청나게 큰 수를 소인수분해할 수 있기 때문이다. 그래서 소수의 곱을 사용하는 RSA를 대체할 양자 내성 알고리즘 (이번에 사용할 MLKem(Kyber)는 그 중 격자기반)을 통해 만들면 앞으로의 시대에도 적절한 암호화를 구성할 수 있을 거라고 생각했다.

하지만 비대칭 키 알고리즘을 파일 암호화에 직접 사용하면 속도가 매우 느리므로 public key를 encapsulation하여 생성된 공유키를 키로 하여 대칭 암호화 알고리즘 (AES)를 사용하고 키 파일에는 공유키 복구를 위해 private key, encapsulation 정보를 저장하면 일단 파일 자체의 암호화와 복호화는 구현한 셈이다.

private key를 평문으로 저장한다고?

복호화를 위해 private key와 public key encapsulation의 결과 중 나오는 데이터를 저장해야 한다고 했는데 이걸 평문으로 저장하면 말짱도루묵이다. 그럼 이 파일을 다시 암호화를 해야하는데, 위에서 RSA 만들때 복호화 키(257이 아닌 소수쪽) 저장할때도 비슷한 생각이 들었었다. 얘는 PBKDF2 를 이용하여 비밀번호를 입력해서 두 정보를 다시 암호화하는 것이 좋을 것이다.

C# 바이너리 직렬화

사실 이건 코틀린으로 하고 있었는데, 코틀린 멀티플랫폼의 데스크톱앱 (Compose) 의 그 마테리얼테마가 너무 맘에 안 들어서 C#으로 옮겼다.

내가 C# 기초를 처음 했을 무렵인 2012~2014년 쯤 객체 직렬화는 BinaryFormatter 를 썼는데, (와 이 새끼는 군대 외박나와서 공부를 했네)

파일입출력 : 네이버 블로그

이게 보안상 취약하기때문에 .NET 5부턴가 deprecated 되었다고 한다. 그래서 뭐 쓰냐고 물어보니 JsonSerializer (C# 빌트인), MessagePack이나 Protobuf (공통프로토콜 사용하는 서드파티)를 사용한다고 한다.

처음에는 귀찮아서 그냥 JsonSerializer를 썼는데 이게 byte[]를 저장할때 Base64로 인코딩해서 저장하기 때문에 크기가 좀 많이 커지는 문제가 있다. 그래서 MessagePack을 사용하기로 했다.

전체적인 흐름

대충 암호화 흐름은 다음과 같다.

  1. MLKem 알고리즘을 이용하여 공개키와 비밀키를 만든다.
  2. 공개키 인캡슐레이션을 통해 공유키와 비밀키와 함께 저장할 인캡슐레이션 정보를 만든다.
  3. 공유키를 AES의 키로 사용하여 원본 파일을 암호화한다.
  4. 입력된 비밀번호를 통해 비밀키와 인캡슐레이션 정보를 암호화하여 저장한다.

복호화 흐름은 다음과 같다.

  1. 입력된 비밀번호를 통해 키 파일을 복호화하여 비밀키와 인캡슐레이션 정보를 복호화한다.
  2. 비밀키와 인캡슐레이션 정보를 통해 공유키를 구한다.
  3. 공유키를 AES의 키로 사용하여 암호화된 파일을 복호화한다.

부가적인 것

파일 전체를 한번에 암호화하는 것은 아무래도 별로일 것 같아 청크로 나눠서 암호화할건데, 이걸 그냥 청크로 나눠서 하면 크게 무리는 없다. 근데 문제는 C#이 2GB가 넘는 파일은 한번에 열어서 메모리에 byte[]로 저장해둘 수 없기 때문에 스트림을 써야하는데 스트림을 쓸 경우 복호화 시 청크의 경계를 알기가 힘들어서 어떻게 해야하나 생각중이다.

끗.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다