공개키 기반 웹 로그인 구현 - ecdsa-recover-pubkey


요약

ECDSA의 Public Key Recovery를 Web crypto API와 함께 사용할 수 있도록 잘 포장해 봤다.

데모

# Warning: it's a UNLICENSED package. Likely you're not licensed to use it.
npm i ecdsa-recover-pubkey

배경

개인정보 보안에 관심을 갖다 보면 비밀번호라는 시스템이 정말 불안하다. 내가 키보드로 입력하는 평문 암호를 대상 웹사이트에서 읽을 수 있으며, 그 이후에 걔네가 적절한 암호화를 해서 저장하는지 여부는 순전히 그들의 선의에 기대야 한다. 여태껏 수많은 평문 비밀번호 유출 보안사고가 발생해 왔으며, 하도 유출이 빈번하다보니 몇몇 비밀번호 관리 프로그램들은 내가 쓰는 비밀번호가 유출된 적 있는지 알려주는 기능마저 있다.

하지만 개발자들이 주로 쓰는 ssh는 전혀 그런 문제가 없다. 왜냐? 공개키 알고리즘을 사용하니까! 근데 여기서 공개키 알고리즘이 뭔지 설명하기 시작하면 너무 길어지니까 설명은 생략한다.

웹에서 그런 공개키 알고리즘을 이용한 로그인을 구현할 수 있을까? 현재 가장 근접한 구현체는 이더리움 지갑이다. 이더리움 주소는 ECDSA의 공개키 역할을 하고, 이더리움 개인키는 말 그대로 개인키 값 바로 그것이다. Web3.js 등의 이더리움 기반 라이브러리는 이를 이용해서 로그인 기능을 구현하고 있다.

TODO: 그림: Metamask가 sign 제공 : 웹사이트에서 sign verify

이걸 웹 표준 기술을 이용해서 동일하게 구현할 수 있을까? Web Crypto API가 ECDSA를 제공하고 있지만, 웹 표준에 정의된 것들은 이더리움의 그것과 살짝씩 다르다.

  • 이더리움은 secp256k1을 쓰지만, web crypto는 지원 안한다. 이 글에선 표준 p256을 대체로 쓴다.
  • 이더리움은 keccak256을 해시 함수로 쓰지만, web crypto는 지원 안한다. 이 글에선 표준 sha256을 대체로 쓴다.
  • 이더리움은 대개 RFC6979을 쓰는듯 하지만, web crypto는 그렇지 않다. 이 글에선 문제되지 않는다.
    • 모든 이더리움 구현이 RFC6979를 쓰진 않는듯 하다. 예를 들면 AWS KMS는 안한다고 써놨음

가장 큰 차이는 Web Crypto API에서 public key recovery 기능이 빠져있다는 점이다. 이더리움은 이것을 이용해서 클라이언트의 signature만 가지고도 유저의 이더리움 주소를 알 수 있는데, web crypto는 이 기능이 없으니까, client가 signature와 함께 자기 자신의 public key를 제공해야 verification이 가능하다. 물론 그렇게 구현해도 되기야 하겠지만, 왠지 이더리움 구현한테 지는 느낌이 드니까 한번 직접 구현해 봤다.

구현

수학적인 디테일은 위키피디아에 설명되어 있다. 하지만 당연히 이미 그것을 라이브러리로 구현해둔 사람들이 있으니 잘 적용만 하면 된다.

Web crypto signature의 구조

ECDSA signature는 r,s 두개의 값으로 구성되어 있는데, sign API로 생성한 signature는 그냥 이 두 값을 붙여놓은 값이 된다. 즉, 64byte signature의 첫 32byte는 r, 다음 32byte는 s이다.

Client측: V 계산하기

https://bitcoin.stackexchange.com/questions/38351/ecdsa-v-r-s-what-is-v

에 의하면 여러 개의 Curve Point가 public key로 존재할 수 있는데, 이걸 구분하기 위해 v 값이 필요한 것으로 알고 있다. elliptic 라이브러리의 구현은 그냥 가능한 v값을 돌면서 public key와 동일한 값이 나오는지 비교하는 방식으로 되어있다.

Server측: pubkey 복원하기

라이브러리에 잘 집어넣으면 뿅 하고 나온다.

결론

하면 된다.