You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Apple App Attest服务端Nonce不匹配问题求助

Apple App Attest服务端Nonce不匹配问题求助

大家好,我现在正在尝试在我的应用中实现Apple的App Attest功能,后端用的是Go语言。我按照指南完成了证书和密钥验证的步骤,结果都正确,但卡在了clientDataHash和OID对象中获取的值不匹配的问题上,怎么都调不对。

我已经尝试过两种方式生成哈希:

  • 直接对服务器挑战进行哈希
  • 对挑战的Base64编码结果进行哈希

查了一些资料,比如W3C和WebAuthn的文档,提到需要用JSON对象来处理挑战,但Apple的官方文档里完全没提这一点。另外我也很困惑:既然客户端在调用DeviceCheck SDK之前就必须知道clientDataHash,那这个哈希对应的origin到底应该是什么?

我把服务端代码精简到了最小可运行示例,里面保留了Apple官方的示例attestation对象(这是公开示例,直接放进来确保大家不需要额外复制粘贴就能测试)。下面是我的代码,运行后生成的nonce始终和预期不匹配:

package main

import (
	"encoding/base64"
	"encoding/asn1"
	"fmt"
	"crypto/sha256"
	"crypto/x509"

	"github.com/fxamacker/cbor/v2"
)

type CertificateChain struct {
	_ struct{} `cbor:",toarray"`
	Leaf []byte
	Intermediate []byte
}

type AttestationStatement struct {
	X5C CertificateChain `cbor:"x5c,toarray"`
	Receipt []byte `cbor:"receipt"`
}

type AttestationObject struct {
	Format string `cbor:"fmt"`
	Statement AttestationStatement `cbor:"attStmt"`
	AuthorizationData []byte `cbor:"authData"`
}

func main() {
	att_b64 := "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"

	// 解码attestation对象
	attBytes, err := base64.RawURLEncoding.DecodeString(att_b64)
	if err != nil {
		fmt.Printf("Failed to decode base64: %v\n", err)
		return
	}

	var attObj AttestationObject
	if err := cbor.Unmarshal(attBytes, &attObj); err != nil {
		fmt.Printf("Failed to unmarshal CBOR: %v\n", err)
		return
	}

	// 生成clientDataHash的两种尝试
	serverChallenge := []byte("my-test-challenge") // 示例挑战
	// 尝试1:直接哈希原始挑战
	hash1 := sha256.Sum256(serverChallenge)
	fmt.Printf("Direct hash of challenge: %x\n", hash1[:])

	// 尝试2:哈希Base64编码后的挑战
	b64Challenge := base64.RawURLEncoding.EncodeToString(serverChallenge)
	hash2 := sha256.Sum256([]byte(b64Challenge))
	fmt.Printf("Hash of Base64 challenge: %x\n", hash2[:])

	// 从receipt中解析出预期的clientDataHash
	var receiptPayload struct {
		ClientDataHash []byte `asn1:"tag:0"`
	}
	if _, err := asn1.Unmarshal(attObj.Statement.Receipt, &receiptPayload); err != nil {
		fmt.Printf("Failed to parse receipt ASN.1: %v\n", err)
		return
	}
	fmt.Printf("Expected clientDataHash from receipt: %x\n", receiptPayload.ClientDataHash)
}

运行这段代码后,生成的两个哈希值都和从receipt中解析出的预期值不匹配。有没有朋友遇到过同样的问题?或者能指点一下Apple App Attest中clientDataHash的正确生成方式,以及对应的origin应该怎么设置吗?

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 10:18:04