직렬화 인터페이스

서명 인터페이스는 문자열에만 서명을 한다. 다른 타입에도 서명을 할 수 있도록 Serializer 클래스에는 파이썬 json 모듈과 비슷한 dumps/loads 인터페이스가 있어서 오브젝트를 문자열로 직렬화 한 다음 서명을 해 준다.

dumps()를 사용해 데이터를 직렬화 하고 서명 한다.

from itsdangerous.serializer import Serializer
s = Serializer("secret-key")
s.dumps([1, 2, 3, 4])
b'[1, 2, 3, 4].r7R9RhGgDPvvWl3iNzLuIIfELmo'

loads() 를 사용해 서명을 검증하고 데이터를 역직렬화 한다.

s.loads('[1, 2, 3, 4].r7R9RhGgDPvvWl3iNzLuIIfELmo')
[1, 2, 3, 4]

기본적으로 데이터를 JSON으로 직렬화 한다. simplejson이 설치돼 있으면 내장 json 모듈 대신 사용한다. 서브클래스를 만들어서 그 내부 직렬화 모듈을 바꿀 수 있다.

서명의 유효 기간을 기록하고 검증하고 싶다면 타임스탬프 사용해 서명하기 절을 보라. URL에 쓰기 안전한 형식으로 직렬화 하고 싶다면 URL에 안전한 직렬화 절을 보라.

솔트

모든 클래스들이 솔트 인자도 받는다. 이름이 오해를 유발할 수도 있을 것 같다. 일반적으로 암호학에서 솔트라고 하면 서명된 결과 문자열과 함께 저장해서 레인보 테이블 검색을 막는 뭔가를 기대할 것이기 때문이다. 그런 솔트는 일반적으로 공개된다.

itsdangerous에서는 원래 장고 구현에서처럼 솔트가 다른 역할을 한다. 일종의 네임스페이스라고 볼 수도 있다. 이 역시 공개돼도 크게 위험하지 않는데, 비밀키 없이는 공격자에게 도움이 안 되기 때문이다.

서명하려는 링크가 두 개 있다고 해 보자. 시스템 상에서 사용자 계정을 활성화할 수 있는 활성화 링크가 있고 사용자의 계정을 유료 계정으로 업그레이드 할 수 있는 업그레이드 링크가 있어서 이메일을 통해 보낸다. 두 경우 모두 사용자 ID에만 서명을 한다면 어느 사용자가 활성 링크 URL의 변수 부분을 재사용해서 계정을 업그레이드 할 수도 있을 것이다. 이 경우 더 많은 정보(가령 용도, 업그레이드 또는 활성)를 서명 대상에 포함시킬 수도 있겠지만 다른 솔트를 쓰는 방법도 가능하다.

from itsdangerous.url_safe import URLSafeSerializer
s1 = URLSafeSerializer("secret-key", salt="activate")
s1.dumps(42)
'NDI.MHQqszw6Wc81wOBQszCrEE_RlzY'
s2 = URLSafeSerializer("secret-key", salt="upgrade")
s2.dumps(42)
'NDI.c0MpsD6gzpilOAeUPra3NShPXsE'

첫 번째 직렬화 객체가 내놓은 데이터를 두 번째 직렬화 객체로 받을 수 없다. 솔트가 다르기 때문이다.

s2.loads(s1.dumps(42))
Traceback (most recent call last):
  ...
itsdangerous.exc.BadSignature: Signature "MHQqszw6Wc81wOBQszCrEE_RlzY" does not match

솔트가 같은 직렬화 객체로만 데이터를 받을 수 있다.

s2.loads(s2.dumps(42))
42

실패 대응

예외에는 유용한 속성들이 있어서 서명 검사가 실패한 경우 페이로드를 살펴볼 수 있다. 그 경우 특별히 주의할 필요가 있는데 그 지점으로 갔다는 건 누군가 데이터를 조작했다는 뜻이기 때문이다. 디버깅 용도에 유용할 수도 있다.

from itsdangerous.serializer import Serializer
from itsdangerous.exc import BadSignature, BadData

s = URLSafeSerializer("secret-key")
decoded_payload = None

try:
    decoded_payload = s.loads(data)
    # 페이로드를 디코딩 했으며 안전함
except BadSignature as e:
    if e.payload is not None:
        try:
            decoded_payload = s.load_payload(e.payload)
        except BadData:
            pass
        # 페이로드를 디코딩 했지만 누군가 서명을 조작했으므로
        # 안전하지 않음. 명시적으로 디코딩(load_payload) 단계가
        # 있는 건 페이로드를 역직렬화 하는 게 안전하지 않을 수도
        # 있기 때문. (json이 아니라 pickle이라고 생각해 보라!)

속성을 들여다봐서 정확히 뭐가 잘못됐는지 알아내려는 게 아니라면 loads_unsafe()를 쓸 수도 있다.

sig_okay, payload = s.loads_unsafe(data)

반환되는 튜플의 첫 번째 항목이 서명이 올바른지 나타내는 불리언이다.

API

class itsdangerous.serializer.Serializer(secret_key, salt=b'itsdangerous', serializer=None, serializer_kwargs=None, signer=None, signer_kwargs=None, fallback_signers=None)

이 클래스는 signer 위에서 직렬화 인터페이스를 제공한다. json/pickle이나 여타 모듈들과 비슷한 API를 제공하지만 내부 구조는 다르게 돼 있다. 기반 파싱 및 적재 구현을 바꾸고 싶다면 load_payload()dump_payload() 함수를 오버라이드 해야 한다.

이 구현에서는 사용 가능한 경우 simplejson을 써서 dump와 load를 한다. 사용 가능하지 않은 경우에는 표준 라이브러리의 json 모듈로 대신한다.

Signer를 교체하거나 동작을 수정하려고 하는 경우에 이 클래스의 서브클래스를 만들 필요가 없다. 생성자에 다른 클래스를 주고 거기 전달할 키워드 인자들을 딕셔너리로 주면 된다.

s = Serializer(signer_kwargs={'key_derivation': 'hmac'})

서명 매개변수를 업그레이드 하면서 사용 중인 기존 서명들이 무효가 되지 않게 하고 싶을 수 있다. 현재 서명 방식으로 서명 검증이 실패할 때 시도할 대체 서명 방식을 줄 수 있다.

fallback_signers에 리스트를 줘서 대체 서명 방식들을 지정할 수 있다. 각 항목은 (signer_kwargs, salt, secret_key로 만든) signer 인스턴스거나, 튜플 (signer_class, signer_kwargs)이거나, signer_kwargs의 딕셔너리일 수 있다.

예를 들어 다음은 SHA-512로 서명하고 SHA-512나 SHA1 중 하나로 서명을 없애는 직렬화 객체다.

s = Serializer(
    signer_kwargs={"digest_method": hashlib.sha512},
    fallback_signers=[{"digest_method": hashlib.sha1}]
)

Changed in version 0.14:: 생성자에 매개변수 signersigner_kwargs가 추가됨.

Changed in version 1.1.0:: fallback_signers 지원이 추가되고 실패 시 기본적으로 SHA-512를 대신 쓰도록 구성됨. 이 대체 동작은 기본적으로 SHA-512를 썼던 1.0.0 릴리스를 썼던 사용자들을 위한 것이다.

default_fallback_signers = [{'digest_method': <built-in function openssl_sha512>}]

기본 대체 서명 방식.

default_serializer = <module 'json' from '/usr/lib/python3.6/json/__init__.py'>

생성자에 직렬화 모듈 내지 클래스를 주지 않으면 이걸 쓴다. 현재 기본은 json이다.

default_signer

alias of itsdangerous.signer.Signer

dump(obj, f, salt=None)

dumps()와 비슷하되 파일로 덤프 한다. 파일 핸들이 내부 직렬화 모듈과 호환돼야 한다.

dump_payload(obj)

인코딩 된 객체를 덤프 한다. 반환 값은 항상 bytes다. 내부 직렬화 모듈에서 텍스트를 반환하면 UTF-8으로 인코딩 된다.

dumps(obj, salt=None)

내부 직렬화 모듈로 직렬화 한 서명된 문자열을 반환한다. 내부 직렬화 모듈의 형식에 따라서 반환 값이 바이트열이나 유니코드열이다.

iter_unsigners(salt=None)

서명 검증에 시도할 서명 방식들을 모두 순회한다. 설정한 서명 방식으로 시작해서 fallback_signers에 지정한 서명 방식 각각을 만든다.

load(f, salt=None)

loads()와 비슷하되 파일로부터 적재한다.

load_payload(payload, serializer=None)

인코딩 된 객체를 적재한다. 페이로드가 유효하지 않으면 함수에서 BadPayload를 던진다. serializer 매개변수를 이용하면 클래스에 저장된 것과 다른 직렬화 방식을 쓸 수 있다. 인코딩 된 payload는 항상 bytes여야 한다.

load_unsafe(f, *args, **kwargs)

loads_unsafe()와 비슷하되 파일로부터 적재한다.

New in version 0.15.

loads(s, salt=None)

dumps()의 반대. 서명 검증이 실패하면 BadSignature를 던진다.

loads_unsafe(s, salt=None)

loads()와 비슷하되 서명 검증이 빠져 있다. 직렬화 모듈 동작 방식에 따라선 아주 위험한 동작일 수도 있다. 반환 값이 단순 페이로드가 아니라 (signature_valid, payload)다. 첫 항목은 서명이 유효한지 여부를 나타내는 불리언이다. 이 함수는 절대 실패하지 않는다.

디버깅용으로만, 그리고 직렬화 모듈에 취약성이 없다는 게 확실할 때만 써야 한다. (예를 들어 pickle 직렬화 모듈에는 쓰지 말아야 한다.)

New in version 0.15.

make_signer(salt=None)

사용할 signer 인스턴스를 새로 만든다. 기본 구현에서는 기반 클래스 Signer를 쓴다.