Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/channels/weixin/src/media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
11 changes: 9 additions & 2 deletions packages/channels/weixin/src/send.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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,
Expand Down
9 changes: 6 additions & 3 deletions packages/channels/weixin/src/send.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
Loading