▶ Cinematic fable · Watch on YouTube ▶ 影片版寓言 · 在 YouTube 观看

盛京城里有一座富甲天下的四海商行,城南则是朝廷重兵把守的官银库。

商行在各省州县置办了上百家分号。各分号若需支取现银周转,向来遵循一道极森严的拨银章程: 分号大掌柜不可亲携现银,只能遣派信使前往商行设在京城中轴的“总舵议事堂”求领一道“拨银勘合”;信使拿到勘合,再转赴城南官银库验引提银。

对于根基深厚的老分号,这道章程固若金汤。因为每位老掌柜手里都藏有一方祖传的精铁私印,官银库亦有底册。只要凭勘合与精铁私印双印合璧,银两便绝无错付之虞。

可商行近年来生意日新月异,招募了数千名轻装走街的年轻脚商。 这些走街小郎腰系布带、肩挎单薄竹篓,游走于勾栏瓦舍与市井闹市之间。他们既没有固定的坚固铺面,更不可能随身怀揣沉重险要的精铁私印——一旦在街巷遇袭,私印失窃,整座商行百年的身家性命便全被贼人攥在了掌心。

没有私印,走街小郎如何提银?总舵起初放宽了限度:只要小郎报上名号,总舵便开出一纸盖有商行大红官印的朱墨勘合,小郎揣着勘合去银库领银便是。

然而,祸端随之而起。

京城的茶楼酒肆里,潜伏着一伙唤作“梁上燕”的窃贼。他们精通市井截道,专盯这群衣着单薄的年轻小郎。 小郎前脚刚从总舵议事堂跨出大门,行至僻静窄巷,后颈便挨了一记闷棍,怀里的朱墨勘合瞬间被摸得一干二净。 窃贼抓起勘合,飞奔南门官银库。银库守卫核验官印分毫不差,当即起关拔门,将白花花的现银全数搬上贼车。等被打晕的小郎苏醒哭喊着赶到时,银库早已人去车空。

短短一月,商行被截去官银十数万两。 大掌柜们聚堂顿足,有人主张从此严禁年轻小郎提银,有人斥责银库守卫失察。

此时,常年镇守银库的一位蒙眼老供奉冷笑了一声: “小郎赤手空拳走街市,本就守不住秘密;可你们的勘合认纸不认人,更是把整副锁匙白白送给了截道的贼匪。”

老供奉从袖中摸出一块未琢的青冈硬木与一盒软朱砂泥,传下一道令天下游贼彻底绝望的“阴阳扣”之法:

“自今日起,凡走街小郎出门求银,切记三步暗规: 第一步,小郎从自个儿怀里摸出一块谁也没见过的随手木料,用短刃在上面随心刻下三道错落有致的暗齿,此物叫阳扣。阳扣贴肉揣进衣襟深处,纵然天塌地陷,绝不拿出来示人; 第二步,小郎将阳扣往湿润的朱砂泥盒上一压,印出一块带着凹凸反纹的坚硬泥印,此物叫阴模。小郎赶赴总舵议事堂,只将阴模递上,并朗声道:‘我此行取银,只认能扣合此阴模之人!’总舵查验属实,便将这枚阴模烙在拨银勘合的正中央; 第三步,拿到勘合后,哪怕在闹市被截匪乱棍夺走,小郎亦不必惊慌——”

三日后,茶楼老贼果然故伎重演。他们伏击了一名年轻小郎,抢走那道赫然盖着阴模的勘合,得意洋洋地冲向城南官银库。

银库铁栅前,老供奉验看勘合,见中央烙着一枚奇巧险绝的阴模,嘴角泛起冷笑,伸手扣住木案: “官印不差,阴模在此。请这位‘掌柜’,从怀里取出能分毫不差咬合此模的阳扣来吧!”

窃贼刹那间面如死灰。 截道只能抢走写在纸面上的勘合与阴模,可那块由小郎在密室随手刻就、天下独一无二的硬木阳扣,依然完好无损地躺在被打晕小郎的衣襟内衬里!

一刻钟后,醒来的小郎在巡街武士护送下赶至银库。他从怀里掏出带着体温的青冈阳扣,往石案上的阴模一推—— 咔嗒! 暗齿与泥印严丝合缝,龙虎相咬! 守卫当场将窃贼反剪锁拿,金锁起旋,整车官银分厘不差地交到了小郎手中。

从此,盛京城千百小郎纵横市井,怀藏阳扣,手递阴模。 任凭暗巷里刀光剑影、窃贼蜂起,官银库的金门再未错开过一次。

— — —

这是什么

这就是现代网络安全与身份认证领域中保护公开客户端免遭劫持的黄金标准——OAuth 2.0 PKCE(Proof Key for Code Exchange,发音为 pixy),正式定义于 RFC 7636。

在标准的 OAuth 2.0 授权码模式(Authorization Code Grant)中,用户在浏览器中向授权服务器(如 Google、GitHub、微信)登录并授权后,授权服务器会通过重定向 URL 将一个临时的授权码(Authorization Code)送回客户端应用,客户端再拿着授权码去后端交换真正的访问令牌(Access Token)。

对于有后端的保密客户端(Confidential Client,如运行在私有服务器上的 Web 应用),客户端可以使用部署在服务器上的 client_secret(如同老分号的精铁私印)来证明自己的身份。 然而,对于公开客户端(Public Client)——如手机移动端 App(iOS/Android)、单页前端应用(SPA / React / Vue),代码完全暴露在不可信的终端设备上,绝对无法安全存储任何密钥(反编译安装包或审查浏览器源码即可轻易窃取)。

在没有 PKCE 的年代,移动端面临着致命的授权码拦截攻击(Authorization Code Interception Attack): 恶意 App 可以通过在操作系统中注册与正规 App 相同的自定义 URL Scheme(如 myapp://oauth-callback),抢先拦截从系统浏览器重定向返回的授权码,随后假冒正规 App 向令牌端点兑换 Token,彻底窃取用户账户数据。

PKCE 采用极具智慧的“一次性动态密码锁”彻底粉碎了这一攻击链条:

  1. 生成阳扣(Code Verifier): 客户端在发起授权请求前,先在本地内存中生成一个高熵的加密随机字符串,称为 code_verifier(43~128 字符);
  2. 压制阴模(Code Challenge): 客户端对 code_verifier 进行单向哈希转换(强制推荐 SHA-256 算法): \(\text{code\_challenge} = \text{BASE64URL}(\text{SHA256}(\text{code\_verifier}))\) 客户端将 code_challenge 连同授权请求发送给授权服务器;
  3. 绑定与流转: 授权服务器记录下该 code_challenge,并将其与生成的授权码 code 强绑定。随后,授权码返回给客户端;
  4. 验证与兑换: 即便恶意应用在操作系统层面劫持了授权码 code,它也仅仅拿到了公开传输的密文。 当客户端向令牌端点发起兑换请求时,必须在请求体中附带原始的 code_verifier。 授权服务器使用 SHA-256 对收到的 code_verifier 重新计算哈希,唯有与此前存储的 code_challenge 丝毫不差时,才发放 Access Token。

为什么重要

  • 公开客户端的零信任基石:无需任何静态密码(Client Secret),即便是开源透明的纯前端单页应用或极易被逆向的移动端 App,也能享受到金融级的身份认证安全;
  • 单向哈希数学不可逆:窃听者即使在网络链路上截获了 code_challenge,根据密码学单向陷门特性,也绝无可能在有效时间内反推出原始的 code_verifier;
  • 防范 CSRF 与会话注入:PKCE 天然将发起建联的“浏览器会话”与最后兑换 Token 的“应用实例”强制绑定在了一起,彻底杜绝了跨上下文攻击;
  • 行业安全强制演进:鉴于 PKCE 卓越的防御效果,最新的 OAuth 2.1 规范已经废弃了传统的纯隐式授权(Implicit Grant),并强制要求所有客户端(包括保密服务端应用)全面推行 PKCE。

隐喻对应表

  • 四海商行总舵议事堂 → 授权服务器(Authorization Server)(负责认证用户身份并签发凭据的核心权限中心)
  • 南门官银库 → 资源服务器(Resource Server / API)(存放受保护数据(金银/业务数据)的受限服务端)
  • 轻装走街的年轻脚商小郎 → 公开客户端(Public Client / SPA / App)(运行在不可信用户设备上、无法安全保存私钥的应用)
  • 老掌柜祖传的精铁私印 → 客户端密钥(Client Secret)(仅能安全保存在私有服务器后端的静态长效密码)
  • 总舵开具的朱墨勘合 → 授权码(Authorization Code)(经浏览器重定向传递的短期一次性凭证)
  • 暗巷里敲闷棍的“梁上燕”窃贼 → 授权码拦截攻击(URL Scheme Hijacking)(恶意软件注册同名协议抢先截获重定向授权码)
  • 小郎随手刻就、贴肉揣着的阳扣 → 代码验证器(code_verifier)(客户端本地生成的临时高强度随机字符串)
  • 压在湿润朱砂盒上的凹凸阴模 → 代码挑战(code_challenge)(经 SHA-256 单向加密哈希处理后的公开摘要)
  • 银库老供奉要求两扣合璧方可领银 → 令牌端点(Token Endpoint)的哈希比对(服务端校验 SHA256(verifier) == challenge)
  • 窃贼空有勘合而无阳扣遭锁拿 → 恶意软件因缺少 code_verifier 兑换失败(无法逆向哈希,攻击者即便拦截授权码也一无所获)

Inside the imperial capital stood the Great Seas Trading Guild, an empire-spanning mercantile syndicate. At the south of the city, heavily fortified by imperial guards, lay the Grand Silver Treasury.

The guild operated hundreds of regional branch houses across the empire. Whenever a regional branch required bullion for operations, it followed an unbending protocol: The regional director could not transport chests of silver across the roads. Instead, he dispatched a trusted courier to the guild’s central hall to obtain an imperial disbursement warrant. With the warrant in hand, the courier proceeded to the southern treasury to withdraw the silver.

For the ancient, established branch offices, this protocol was impenetrable. Each veteran master held an ancestral iron cipher stamp, the impression of which was cataloged in the treasury’s master register. By pairing the parchment warrant with the impression of the iron stamp, the silver was never misdirected.

In recent years, however, the guild expanded rapidly, deploying thousands of nimble, traveling peddlers. These young couriers wore simple linen tunics and carried unadorned bamboo satchels, weaving through crowded bazaars and busy alleyways. They possessed neither fortified vaults nor ancestral iron stamps. Indeed, carrying a permanent iron stamp upon the open streets was unthinkable—should a youth be ambushed and the stamp lost, the guild’s entire century of wealth would fall into bandit hands.

Without a private stamp, how could a traveling courier draw silver? The guild council initially relaxed the rule: so long as the youth identified himself, the central hall would issue a red-ink disbursement warrant bearing the guild’s public seal; the youth took the warrant to the treasury and claimed the silver.

Disaster struck almost immediately.

In the taverns and tea stalls along the capital’s alleys lurked a notorious band of highwaymen known as the Rooftop Swallows. They specialized in intercepting travelers in narrow lanes. No sooner had a young courier stepped out of the guild gates than he was struck from behind in an alley, and the red-ink warrant was plucked from his breast. The thieves sprinted to the southern treasury. The guards verified the guild seal on the warrant, swung open the vault gates, and rolled barrels of silver onto the thieves’ carts. By the time the bruised youth staggered to the treasury gates, the vault was empty and the carts were miles away.

Within a single month, the syndicate lost hundreds of thousands of ounces of silver. The senior directors gathered in panic, some demanding that young couriers be banned from collecting silver, while others blamed the treasury guards for negligence.

At that moment, a blind elder who had guarded the treasury vaults for fifty years gave a dry laugh from the corner: “A youth walking the streets with bare hands can keep no secret; yet your warrants identify only the paper, not the man who requested it. You are handing the keys to the thieves.”

The elder drew from his sleeve a block of uncut hardwood and a small ceramic tin of moist cinnabar clay. He laid down the rule of the Yin-Yang Toggle, a device that would drive street thieves to despair:

“From this day forth, whenever a young courier sets out for silver, he must obey three hidden rules: First, the youth takes a scrap of wood and, with his pocket knife, carves three unpredictable notches into it. This is the Yang Toggle. He tucks the wooden toggle deep inside the inner lining of his belt. Though the sky collapse, he never exposes it to another soul; Second, the youth presses the carved toggle into the tin of soft cinnabar clay, leaving a negative impression with reverse grooves. This is the Yin Mold. The youth hastens to the guild hall, presents only the clay mold, and declares: ‘My withdrawal may be released only to whoever can mate with this mold!’ The guild records the request and stamps this clay impression into the center of the disbursement warrant; Third, once the warrant is issued, even if highwaymen strike the youth down and snatch the parchment, the youth need not despair—”

Three days later, the thieves struck again. They ambushed a young courier in an alley, seized the warrant bearing the cinnabar impression, and rushed to the southern treasury with greedy grins.

Before the heavy iron grates, the blind elder examined the warrant, fingering the intricate hollows of the cinnabar seal. A cold smile touched his lips: “The guild seal is authentic, and the Yin Mold is here. Now, ‘master merchant,’ draw from your breast the Yang Toggle that locks into these teeth with microscopic perfection!”

The thief turned the color of ash. Highway robbery can steal a warrant and the clay impression stamped upon it, but the unique wooden toggle carved inside the private room minutes earlier remained securely wrapped inside the fallen courier’s belt!

Moments later, the bruised youth arrived under the escort of the city watch. From his belt lining he withdrew the warm wooden toggle and slid it into the clay imprint on the table— Click! The wooden teeth engaged the clay hollows with flawless precision. The guards seized the thief in chains, the heavy vault doors groaned open, and every coin of silver was delivered into the youth’s hands.

From that day forth, thousands of young couriers crisscrossed the imperial markets, carrying the Yang Toggle in their belts while sending the Yin Mold on ahead. Though shadows gathered in the alleys and thieves swarmed the avenues, the treasury gates were never breached again.

— — —

What it is

This is the gold standard for securing public applications in modern identity and access control: OAuth 2.0 PKCE (Proof Key for Code Exchange, pronounced “pixy”), formalized in RFC 7636.

In the standard OAuth 2.0 Authorization Code Grant, after a user logs in and consents at an authorization server (e.g., Google, GitHub, Okta), the authorization server redirects back to the client application with a temporary Authorization Code. The client application then exchanges this code for a long-lived Access Token.

For confidential clients running on secure back-end servers, the application uses a provisioned client_secret (like the regional director’s ancestral iron stamp) to authenticate itself. However, Public Clients—such as mobile applications (iOS / Android) and Single-Page Applications (SPAs built with React or Vue)—execute on untrusted user devices where code is completely public. They cannot securely store a static client secret, as decompiling the mobile binary or inspecting the browser source code instantly exposes it.

Without PKCE, mobile platforms are vulnerable to the Authorization Code Interception Attack: A malicious app installed on the device can register the exact same custom URL scheme (such as myapp://oauth-callback) as the legitimate app. When the operating system returns the authorization code from the browser, the malicious app intercepts the redirect, presents the stolen code to the token endpoint, and steals the user’s access token.

PKCE completely eliminates this threat using a dynamic, one-time cryptographic challenge:

  1. The Yang Toggle (code_verifier): Before initiating authorization, the client generates a high-entropy, cryptographically random secret string called the code_verifier (between 43 and 128 characters), held purely in local memory;
  2. The Yin Mold (code_challenge): The client calculates a one-way cryptographic hash of the verifier using SHA-256: \(\text{code\_challenge} = \text{BASE64URL}(\text{SHA256}(\text{code\_verifier}))\) The client transmits this code_challenge to the authorization server in the initial redirect;
  3. Binding: The authorization server records the code_challenge and associates it with the issued authorization code;
  4. Verification: Even if a malicious application intercepts the authorization code in transit, it possesses only the code and the public challenge. When the legitimate client exchanges the authorization code at the token endpoint, it presents the raw code_verifier. The authorization server runs SHA-256 on the presented code_verifier and verifies that it reproduces the stored code_challenge. Only upon a match is the Access Token released.

Why it matters

  • Zero-Trust for Public Clients: Eliminates the need for static client secrets, providing military-grade identity verification for open-source frontends and mobile apps;
  • One-Way Cryptographic Trapdoor: An attacker intercepting the code_challenge on the wire cannot reverse SHA-256 to derive the original code_verifier;
  • Inherent CSRF and Session Injection Defense: Binds the browser authorization session directly to the specific application instance that initiated the request;
  • Universal Industry Standard: The modern OAuth 2.1 specification deprecates the old Implicit Flow entirely and mandates PKCE for all OAuth clients, including server-side confidential applications.

Metaphor mapping

  • Great Seas Trading Guild central hall → Authorization Server (Central authority authenticating users and issuing access tokens)
  • Southern Imperial Silver Treasury → Resource Server / Protected API (The service housing protected user resources and private endpoints)
  • Young traveling peddler with a bamboo satchel → Public Client (Mobile App / SPA) (An application running on an untrusted device unable to hold secrets)
  • Elder master’s ancestral iron stamp → client_secret (Confidential Client credential) (A long-lived static secret that must never be embedded in public code)
  • Disbursement warrant in red ink → Authorization Code (code) (A temporary, single-use authorization code returned via redirect)
  • Highwayman ambushing the youth in an alley → Authorization Code Interception Attack (Malicious app intercepting redirect via custom URL scheme hijacking)
  • Wooden Yang Toggle carved in private → code_verifier (A locally generated, unshared high-entropy cryptographic secret)
  • Cinnabar clay impression of the toggle → code_challenge (SHA-256 hash) (The one-way mathematical hash sent over the wire with the request)
  • Treasury elder requiring the toggle to fit the mold → Token Endpoint verification (Server verifying that SHA256(verifier) == challenge)
  • Thief caught empty-handed without the toggle → Intercepted code rendered useless (Without the code_verifier, the stolen authorization code cannot be redeemed)
Daily Fables每日寓言 2026-09-13