auth-source와 EasyPG 기본 제공 패키지를 사용한 비밀 번호를 포함한 계정 정보 관리

4 minute read

/emacsian/assets/2023-04-22-auth-source-easypg-00.jpg

auth-source란?

The auth-source library is simply a way for Emacs and Gnus, among others, to answer the old burning question “What are my user name and password?”

(This is different from the old question about burning “Where is the fire extinguisher, please?”.)

The auth-source library supports more than just the user name or the password (known as the secret).

계정 정보를 저장하는 기본 제공(built-in) 패키지다.

계정 정보는 어디에 저장하고 어떻게 읽나?

“Netrc” files are a de facto standard. They look like this:

machine mymachine login myloginname password mypassword port myport

표준인가보다.

auth-source-search 함수를 사용해 쿼리(query)할 때, 다음과 같은 키워드를 사용한다.

  • machine: :host
  • port: :port
  • login: :user

~/.authinfo 파일 내용을 다음과 같이 채운다.

machine www.example.com login emacsian password SUPERSECRET
machine www.example2.com login emacsian2 password SUPERSECRET2
machine www.example2.com login emacsian2_nopassword

읽으려면 auth-source-search 함수를 사용한다

(message "%S" (auth-source-search :max 10))

:max 키워드로 검색 결과 개수를 설정할 수 있다. 기본값은 1이다.

((:host "www.example.com"
  :user "emacsian"
  :secret #[0
            "[bytes...]"
            ["ERBpL2TcwCaxxqODSSrD4Q==-WlbcDtBn/S4mnV8rfbnX+A==" (nil) auth-source--deobfuscate]
            3])
 (:host "www.example2.com"
  :user "emacsian2"
  :secret #[0
            "[bytes...]"
            ["jGREb+xUOYGWkLX69IxtnA==-RCCaWu7NT0FYnXSil7207A==" (nil) auth-source--deobfuscate]
            3])
 (:host "www.example2.com" :user "emacsian2_nopassword"))

이렇게 전체 결과를 받아서 다시 찾을 수 있다. 다행히 auth-source-serach 함수는 검색 옵션을 지원한다.

(message "%S"
         (auth-source-search :host "www.example2.com"
                             :max 2))
((:host "www.example2.com"
  :user "emacsian2"
  :secret #[0
            "[bytes..]"
            ["mJQ+p4XGZ18Xq0B76I8wdA==-mhp1X0VTGWPlTtSvCP4R5g==" (nil) auth-source--deobfuscate]
            3])
 (:host "www.example2.com"
  :user "emacsian2_nopassword"))

:host, :user, :port 키워드를 사용해서 auth-source 를 검색할 수 있다. password 같은 필드가 없을 때, 검색에서 걸러낼 방법이 있을까?

(message "%S"
         (auth-source-search :host "www.example2.com"
                             :requires '(:secret)))
((:host "www.example2.com"
  :user "emacsian2"
  :secret #[0
            "[bytes...]"
            ["Kz7kBpHl+nHykyHbjmOA+A==-UDLWtWmsZSNhHX8sN2uUcw==" (nil) auth-source--deobfuscate]
            3]))

:requires 옵션을 사용하면 된다. 하지만 기대한 대로 동작하지 않는다.

(message "%S"
         (auth-source-search :host "www.example.com"
                             :user "emacsian"
                             :requires '(:secret)))
((:host "www.example2.com" :user "emacsian2_nopassword"))

emacsian2_nopassword 유저 설정에는 패스워드가 없어서 :requires '(:secret) 인자를 넘기면 결과 리스트가 비어있길 기대했는데, :secret 필드가 없는 리스트가 리턴됐다. 검색 결과가 여러 개일 때는 잘 동작하는데, 단일 결과면 :requires 인자로 넘긴 필드가 없어도 리턴한다.

(message "%S"
         (auth-source-search :host "www.example.com"
                             :user "emacsian"
                             :requires '(:secret)))

아쉽지만 어떤 값을 읽으려고 함수를 호출한다면 설명이 코드 자체로 설명할 수 있으므로 :requires 필드에 값은 꼭 채운다.

이제 필요한 password를 읽어보자.

(plist-get (car
            (auth-source-search
             :host "www.example.com"
             :user "emacsian"
             :requires '(:secret)))
           :secret)
#[0 "[bytes...]" ["MChbrMSOpm6P7T5iqXplEg==-pn9MgEU3lTzJbUQGP+8QgA==" (nil) auth-source--deobfuscate] 3]

auth-source-search 리턴 값이 property list(plist)를 요소(element)로 가지는 리스트이므로 car 함수를 사용해 첫 번째 요소를 가져온다. 그 후 plist-get 함수를 사용해 :secret 값을 가져온다. 하지만 결과가 이상한 거네? password는 어디에 간 거야?

(funcall (plist-get
          (car (auth-source-search
                :host "www.example.com"
                :user "emacsian"
                :requires '(:secret)))
          :secret))
SUPERSECRET

함수 형태로 리턴하기 때문에 funcall 함수를 사용해서 호출하면 password를 구할 수 있다. 메모리에 password를 그냥 들고 있지 않는다. 철저하네.

로컬 설정을 담아서 사용하려면?

ohyecloudy/dotfiles 저장소로 버전 컨트롤하지 않는 설정을 예전에는 init.el.local 식으로 만들어서 있으면 로드하고 아니면 무시하는 식으로 사용했다. 저런 로컬 파일에 들어가는 설정이 거진 key-value라서 auth-source 패키지로 관리할 수 있겠단 생각을 했다.

machine myjira login api-base-url password https://www.myoffice.com
machine myjira login project password AWESOME
machine myjira login access-key password SUPERSECRET

이런 식으로 ~/.authinfo 파일을 만든다.

(funcall
 (plist-get
  (nth 0 (auth-source-search
          :host "myjira"
          :user "api-base-url"
          :requires '(:secret)))
  :secret))

그 후 로컬 설정을 로드할 때, auth-source 로부터 password를 획득하는 식으로 로드해서 사용한다.

계정 정보를 gpg로 암호화해서 관리하기

비밀번호를 포함한 계정 정보를 평문(plain text)으로 관리하긴 찝찝하니 GnuPG(GNU Privacy Guard, GPG)로 암호화해서 로컬에 보관하자. emacs에는 다 있다. 그냥 gpg를 사용할 필요 없이 알아서 다 해주는 기본 제공(built-in) 패키지인 EasyPG를 사용하면 된다.

One of the nicest features of Emacs’s GnuPG support is that it transparently decrypts and re-encrypts files you open and save in Emacs.

The EasyPG package hooks into Emacs and will, transparently, detect GnuPG files. The variable auto-mode-alist controls how Emacs assigns major modes when you open certain files; in this instance, any file that ends with .gpg is treated as a GnuPG file in Emacs.

Keeping Secrets in Emacs with GnuPG and Auth Sources - Mastering Emacs - mast…

EasyPG 패키지가 다 해준다. 암호화 복호화를 알아서 다 해주기에 그냥 편집하고 저장하면 된다.

공개키 암호 방식(Public-key cryptography, asymmetric cryptography)에 사용할 공개키(public key)와 개인 키(private key)가 없다면 만든다.

$ gpg --gen-key
gpg (GnuPG) 2.2.29-unknown; Copyright (C) 2021 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Note: Use "gpg --full-generate-key" for a full featured key generation dialog.

GnuPG needs to construct a user ID to identify your key.

Real name: ohyecloudy
Email address: ohyecloudy@gmail.com
You selected this USER-ID:
    "ohyecloudy <ohyecloudy@gmail.com>"

Change (N)ame, (E)mail, or (O)kay/(Q)uit? O
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
gpg: key 01713EFDFAE1CE53 marked as ultimately trusted
gpg: directory '/c/Users/ohyecloudy/.gnupg/openpgp-revocs.d' created
gpg: revocation certificate stored as '/c/Users/ohyecloudy/.gnupg/openpgp-revocs.d/82EB25B59EDFA30FEAAFB60B01713EFDFAE1CE53.rev'
public and secret key created and signed.

pub   rsa3072 2023-01-24 [SC] [expires: 2025-01-23]
      82EB25B59EDFA30FEAAFB60B01713EFDFAE1CE53
uid                      ohyecloudy <ohyecloudy@gmail.com>
sub   rsa3072 2023-01-24 [E] [expires: 2025-01-23]

gpg --gen-key 를 실행해 키를 만든다. ~/.gnupg 디렉터리에 파일이 생성된다.

$ gpg --list-keys
gpg: checking the trustdb
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
gpg: next trustdb check due at 2025-01-23
/c/Users/ohyecloudy/.gnupg/pubring.kbx
------------------------------------
pub   rsa3072 2023-01-24 [SC] [expires: 2025-01-23]
      82EB25B59EDFA30FEAAFB60B01713EFDFAE1CE53
uid           [ultimate] ohyecloudy <ohyecloudy@gmail.com>
sub   rsa3072 2023-01-24 [E] [expires: 2025-01-23]

gpg --list-keys 로 키를 조회할 수 있다. 이제 .authinfo 파일을 gpg로 암호화를 해보자.

~/.authinfo.gpg 파일 내용을 다음과 같이 채운다.

# -*- epa-file-encrypt-to: ("ohyecloudy@gmail.com") -*-

machine www.example.com login emacsian password SUPERSECRET
machine www.example2.com login emacsian2 password SUPERSECRET2
machine www.example2.com login emacsian2_nopassword

주석을 사용해서 공개 키(public key)로 암호화할 것이라고 알린다. 파일을 저장하면 .gpg 확장자를 인식해서 암호화해서 저장한다.

$ cat ~/.authinfo.gpg
▒▒2▒Q▒▒b?
         ▒)▒▒▒W▒<~VjkbG▒▒q7▒▒▒▒a:▒6Kd ▒i▒▒p1▒▒1▒▒jkL▒▒<▒▒▒▒Hk▒-E(Y?@▒▒D*▒▒T▒:ω▒*▒~▒B1s▒▒▒~{O▒^▒▒m▒k▒k▒▒!i▒4▒J▒▒▒▒Rp▒.QL4▒]v7▒\m▒▒▒▒#▒L▒7▒▒@L▒
▒$dV*▒-▒▒▒`▒▒▒▒▒|x▒N▒▒▒▒v▒?▒▒1x▒?\▒▒d▒▒▒▒▒J?▒▒%Q▒?)▒▒g-▒▒▒,7▒<▒▒)~$▒▒▒#%K▒<?▒q▒▒▒fJ▒lY9▒▒▒.K▒▒▒?▒▒jwQRC""/;J'▒▒▒bR▒v▒M▒▒-▒▒▒b▒=▒▒▒▒(▒b▒▒<w_▒▒rL▒sD▒▒▒}▒▒%▒q[T▒▒K ▒ww▒O.▒<'[l|▒
            ▒▒D▒▒O?}?▒▒N        B?*\▒▒▒▒▒)▒
                                           1D?>rH▒C▒rb+▒(>4▒L▒_▒m▒??.▒▒?f▒hi^??w3▒▒8}I~▒▒"▒▒:?
             w▒▒5U▒/▒▒?▒AR▒-u

암호화가 되었다. EasyPG 패키지가 파일을 열 때는 복호화를 해주므로 emacs에서는 파일을 열어서 편집하고 저장하면 된다.

C-x C-s C-x C-c