使用COSE-JAVA验证欧盟数字新冠通行证CBOR消息签名失败求助
问题根因分析
- COSE消息类型错误:欧盟数字新冠通行证使用的是带单签名的
Sign1Message结构,你当前代码用的Encrypt0Message是加密消息类型,完全不匹配,导致解析出来的签名字段为空,后续转换DER格式时触发空指针。 - 签名验证逻辑错误:你使用
CounterSign(副签逻辑)来验证主签名,不符合COSE Sign1的验证流程。 - 潜在的公钥加载风险:
getPublicKey方法异常时会返回null,没有做非空校验,如果公钥加载失败也会触发后续空指针。
适配Android 7.1的可行解决方案
首先做依赖适配:Android7.1自带的BouncyCastle provider版本较低,建议引入适配低版本的依赖,使用org.bouncycastle:bcprov-jdk15on:1.68版本,COSE-JAVA使用v1.1.0版本即可,不要用最新高版本避免兼容性问题。BouncyCastle的初始化建议放到Application的onCreate方法中全局执行一次即可,不要每次加载公钥都重复操作。
修改后的解码验证代码
public static String DecodeMessage(byte[] data) throws Exception { // 替换为Sign1Message解码,匹配DCC的COSE结构 Sign1Message msg = (Sign1Message) Sign1Message.DecodeFromBytes(data); // 公钥非空校验 PublicKey key = getPublicKey("你的公钥PEM内容"); if (key == null) { throw new IllegalArgumentException("公钥加载失败"); } OneKey oneKey = new OneKey(key, null); // 直接验证Sign1签名,无需手动添加算法头 boolean verifyPass = msg.validate(oneKey); if (!verifyPass) { throw new SecurityException("签名验证失败"); } // 解析证书内容 CborMap cborMap = CborMap.createFromCborByteArray(msg.GetContent()); return cborMap.toJsonString(); }
修改后的公钥加载代码
public static PublicKey getPublicKey(String keyData) { try { String fullPem = "-----BEGIN EC PUBLIC KEY-----\n" + keyData + "\n-----END EC PUBLIC KEY-----"; PemReader pemReader = new PemReader(new StringReader(fullPem)); PemObject spki = pemReader.readPemObject(); pemReader.close(); byte[] content = spki.getContent(); X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(content); KeyFactory keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME); return keyFactory.generatePublic(pubKeySpec); } catch (Exception ex) { ex.printStackTrace(); return null; } }
额外注意事项
如果修改后仍出现签名转换的空指针,可手动实现ECDSA concat签名转DER的逻辑替换库内方法,欧盟DCC的签名是raw concat格式(r和s各32字节),自己实现转换逻辑可以避免库内方法异常返回null的问题。另外Android7.1上不要使用Java 8及以上的新语法特性,避免编译后无法在低版本系统运行。
内容的提问来源于stack exchange,提问作者nighthawk
相关产品推荐
相关产品推荐

