메인 콘텐츠로 건너뛰기
Crypto는 고급 내부 구현입니다. 일반적인 게시·피드 취득·알림 등에는 필요 없습니다. AT Protocol의 저수준 서명 검증이나 OAuth 구현을 구축하는 경우에 참조하세요.

AT Protocol에서의 암호화 개요

AT Protocol은 분산형 소셜 네트워크를 실현하기 위해 타원 곡선 암호(ECC)를 전면적으로 채택하고 있습니다. 주된 용도는 다음과 같습니다.
곡선알고리즘주된 용도
secp256r1 (P-256)ES256OAuth(DPoP, Client Assertion)
secp256k1 (K256)ES256KFeed Generator, Labeler의 서명 검증

키 페어 클래스

AbstractKeypair

AbstractKeypair는 P256 / K256의 공통 기반 클래스입니다. 내부에서는 phpseclib3를 사용합니다.
// 새로운 키 페어를 생성
$keypair = P256::create();
$keypair = K256::create();

// URL-safe Base64 인코딩의 비밀 키에서 로드
$keypair = P256::load($base64PrivateKey);

// PEM 형식으로 취득
$privatePem = $keypair->privatePEM();
$publicPem  = $keypair->publicPEM();

// JWK(JSON Web Key)로 변환
$jwk = $keypair->toJWK();

P256 (secp256r1)

use Revolution\Bluesky\Crypto\P256;

$keypair = P256::create();
OAuth(DPoP / Client Assertion)에서 사용됩니다. OAuthKey는 P256을 상속하며, config('bluesky.oauth.private_key')에서 비밀 키를 로드합니다. 새로운 OAuth 비밀 키를 생성하는 Artisan 명령어도 준비되어 있습니다.
php artisan bluesky:new-private-key

K256 (secp256k1)

use Revolution\Bluesky\Crypto\K256;

$keypair = K256::create();
Feed Generator나 Labeler의 인증에서 사용됩니다. Bluesky의 PDS / Relay가 이 곡선으로 서명을 검증합니다.

DidKey

DidKey 클래스는 공개 키를 did:key 형식으로 인코드·디코드합니다.

did:key 형식이란

AT Protocol에서는 공개 키를 did:key:z...라는 문자열로 표현합니다. 이는 공개 키에 곡선 식별자를 부가하여 Base58btc로 멀티베이스 인코드한 것입니다.

공개 키를 did:key로 인코드

use Revolution\Bluesky\Crypto\DidKey;
use Revolution\Bluesky\Crypto\K256;

$keypair = K256::create();
$publicPem = $keypair->publicPEM();

// did:key 형식으로 변환
$didKey = DidKey::format($publicPem);
// => "did:key:zQ3s..."

DID Document에서 공개 키를 파싱

use Revolution\Bluesky\Crypto\DidKey;
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\Support\DidDocument;

$didDoc = DidDocument::make(
    Bluesky::identity()->resolveDID('did:plc:***')->json()
);

// DID Document의 publicKey(multibase)에서 phpseclib3의 공개 키 객체를 생성
$publicKey = DidKey::parse($didDoc->publicKey());

JsonWebToken (JWT)

JsonWebToken 클래스는 JWT의 인코드·디코드를 제공합니다.
use Revolution\Bluesky\Crypto\JsonWebToken;

// JWT를 생성(비밀 키 PEM으로 서명)
$token = JsonWebToken::encode(
    payload: ['iss' => 'did:plc:***', 'aud' => 'https://bsky.social', 'exp' => time() + 60],
    key: $privatePem,
    alg: 'ES256',
);

// JWT를 디코드(서명 검증 없음)
$payload = JsonWebToken::decode($token);

DPoP (Demonstrated Proof of Possession)

DPoP는 OAuth 액세스 토큰을 특정 클라이언트 키 페어에 결부시키는 보안 메커니즘입니다. 토큰의 리플레이 공격을 방지합니다. DPoP 클래스는 내부에서 사용되며, 일반적으로 직접 호출할 필요가 없습니다. OAuthAgent 미들웨어가 자동으로 DPoP 헤더를 부여합니다.
// DPoP 프루프 생성(OAuth 토큰 요청용)
$proof = DPoP::authProof(
    jwk: $jsonWebKey,
    url: 'https://bsky.social/oauth/token',
    method: 'POST',
    nonce: $nonce,
);

// DPoP 프루프 생성(API 요청용, ath 포함)
$proof = DPoP::apiProof(
    jwk: $jsonWebKey,
    url: 'https://api.bsky.app/xrpc/...',
    method: 'GET',
    token: $accessToken,
    nonce: $nonce,
);

Signature (서명 포맷 변환)

AT Protocol은 64바이트의 컴팩트 서명 형식을 사용하지만, phpseclib3은 ASN.1 DER 형식을 반환합니다. Signature 클래스는 이 변환을 담당합니다.
use Revolution\Bluesky\Crypto\Signature;

// ASN.1 DER → 컴팩트(64바이트)
$compact = Signature::toCompact($derSignature);

// 컴팩트 → ASN.1 DER
$der = Signature::fromCompact($compactSignature);

JsonWebKey

JsonWebKey는 JWK(JSON Web Key)를 나타내는 클래스입니다. DPoP 프루프 생성에 사용됩니다.
use Revolution\Bluesky\Crypto\JsonWebKey;

// 키 페어에서 JWK를 생성
$jwk = $keypair->toJWK();

// 또는 직접 인스턴스화
$jwk = JsonWebKey::load(['kty' => 'EC', 'crv' => 'P-256', ...]);

OAuthKey

OAuthKey는 P256을 상속한 OAuth 전용 키 클래스이며, config('bluesky.oauth.private_key') 값을 비밀 키로 사용합니다.
use Revolution\Bluesky\Crypto\OAuthKey;

// 설정에서 비밀 키를 로드
$oauthKey = OAuthKey::load();

// Client Assertion JWT의 서명에 사용됨
$jwk = $oauthKey->toJWK();
일반적으로 Bluesky Socialite 내부에서 자동으로 처리됩니다.

참고 링크

Source: src/Crypto/
마지막 수정일 2026년 7월 13일