From a522dba21b3c1d11ed3d02087a35dfb12c7f8ab4 Mon Sep 17 00:00:00 2001 From: yiliang114 <1204183885@qq.com> Date: Sat, 23 May 2026 17:19:36 +0800 Subject: [PATCH] fix(weixin): send decryptable image payloads --- packages/channels/weixin/src/media.ts | 4 ++-- packages/channels/weixin/src/send.test.ts | 11 +++++++++-- packages/channels/weixin/src/send.ts | 9 ++++++--- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/channels/weixin/src/media.ts b/packages/channels/weixin/src/media.ts index 93dcd35fb05..7e079a760a1 100644 --- a/packages/channels/weixin/src/media.ts +++ b/packages/channels/weixin/src/media.ts @@ -19,8 +19,8 @@ function decryptAesEcb(ciphertext: Buffer, key: Buffer): Buffer { /** * Parse aes_key from CDNMedia into a raw 16-byte Buffer. * Two encodings exist: - * - base64(raw 16 bytes) → images - * - base64(hex string of 16 bytes) → file/voice/video + * - base64(raw 16 bytes) + * - base64(hex string of 16 bytes) */ export function parseAesKey(aesKeyBase64: string): Buffer { const decoded = Buffer.from(aesKeyBase64, 'base64'); diff --git a/packages/channels/weixin/src/send.test.ts b/packages/channels/weixin/src/send.test.ts index 3d3c275c8f4..aa353f92f3e 100644 --- a/packages/channels/weixin/src/send.test.ts +++ b/packages/channels/weixin/src/send.test.ts @@ -325,8 +325,13 @@ describe('sendImage', () => { expectedEncrypted, ); - // Step 4: send message with image_item using CDN's x-encrypted-param - const expectedAesKeyBase64 = aesKeyBytes.toString('base64'); + // Step 4: send message with image_item using CDN's x-encrypted-param. + // WeChat expects images to include the hex key both directly and + // base64-encoded in the media payload. + const expectedAesKeyBase64 = Buffer.from( + expectedAesKeyHex, + 'ascii', + ).toString('base64'); expect(mockSendMessage).toHaveBeenCalledWith( 'https://api.example.com', 'token-abc', @@ -337,6 +342,8 @@ describe('sendImage', () => { expect.objectContaining({ type: 2, // MessageItemType.IMAGE image_item: expect.objectContaining({ + aeskey: expectedAesKeyHex, + mid_size: encryptedSize, media: { encrypt_query_param: 'cdn-encrypt-param', aes_key: expectedAesKeyBase64, diff --git a/packages/channels/weixin/src/send.ts b/packages/channels/weixin/src/send.ts index 27c8ab5fddd..416f12ddb26 100644 --- a/packages/channels/weixin/src/send.ts +++ b/packages/channels/weixin/src/send.ts @@ -231,9 +231,10 @@ export async function sendImage(params: { const encrypted = encryptAesEcb(fileBuffer, aesKeyBytes); const cdnEncryptParam = await uploadToCdn(uploadParam, filekey, encrypted); - // Step 4: send message with image_item using CDN's x-encrypted-param - // aes_key: base64(raw 16 bytes) for images per protocol - const aesKeyBase64 = aesKeyBytes.toString('base64'); + // Step 4: send message with image_item using CDN's x-encrypted-param. + // WeChat image messages expect the AES key as a hex string, with media.aes_key + // carrying base64(hex string), not base64(raw bytes). + const aesKeyBase64 = Buffer.from(aesKeyHex, 'ascii').toString('base64'); await sendMessage(baseUrl, token, { to_user_id: to, @@ -246,6 +247,8 @@ export async function sendImage(params: { { type: MessageItemType.IMAGE, image_item: { + aeskey: aesKeyHex, + mid_size: encryptedSize, media: { encrypt_query_param: cdnEncryptParam, aes_key: aesKeyBase64,