# （可选）用户身份验证服务接入准备

如不涉及身份验证服务接入，可跳过该章节。

#### 开启用户身份验证服务权限开关

开发者可登录AppGallery Connect，在“项目设置 > 开放能力管理”的“鸿蒙支付服务”中开启身份验证服务相关权限开关。

开启身份验证服务相关权限开关时，开发者需签署一份“开发者协议”，开发者同意协议并提交申请资料后需要等待审核（审核周期一般在1-3个工作日）通过后才能使用相关服务。

#### 上传开发者公钥及下载华为公钥

开发者可登录AppGallery Connect，在“鸿蒙支付服务 > 身份验证服务”菜单中的“公钥管理”页签下完成开发者证书的上传以及华为公钥证书下载。

证书使用如图所示：

证书说明如下：

| 证书 | 获取方式 | 内容说明及使用场景 |
| --- | --- | --- |
| 华为加密公钥 | AppGallery Connect的“鸿蒙支付服务>身份验证服务>公钥管理”下载 | 华为支付服务器使用SM2加密算法生成的证书公钥。 使用场景： 开发者可用对应的公钥证书对请求开放API接口的隐私字段进行加密，华为支付服务器使用配对的私钥证书对隐私字段进行解密。 |
| 华为签名公钥 | AppGallery Connect的“鸿蒙支付服务>身份验证服务>公钥管理”下载 | 华为支付服务器使用SM2加密算法生成的证书公钥。 使用场景： 华为支付服务器使用配对的私钥证书对响应报文进行加签， 开发者用于对开放API接口响应报文验签使用，具体验签方式请参见验签规则。 |
| 开发者公钥（加密） | 开发者生成 | 开发者使用SM2加密算法生成的证书公钥。需登录AppGallery Connect，在“鸿蒙支付服务>身份验证服务>公钥管理”上传（公钥类型为加密）。 使用场景： 开发者上传后生成证书Id（developerEncKeyId），开发者请求开放API接口时可通过PayDevAuth请求头传递，指定给华为支付服务器用于对开放API接口响应的隐私字段加密。 |
| 开发者公钥（签名） | 开发者生成 | 开发者使用SM2加密算法生成的证书公钥。需登录AppGallery Connect，在“鸿蒙支付服务>身份验证服务>公钥管理”上传（公钥类型为签名）。 使用场景： 开发者上传后生成证书Id（developerSignKeyId），开发者请求开放API接口时可通过PayDevAuth请求头传递，指定给华为支付服务器用于对开放API接口请求报文进行验签。 |

#### SM2公私钥对生成示例代码参考

*   只支持ASN.1格式的SM2公私钥对（以下示例代码为服务端生成示例）。如需在应用端生成ASN.1格式SM2公私钥对（公钥91字节，私钥51字节）可参考：

1.  随机生成非对称密钥对(ArkTS)（应用端生成的公私钥对可能无法在服务端使用）。
2.  参考数据编码格式差异将生成的秘钥对转成16进制hex格式。

*   生成的SM2公私钥对，还请先自测验证加解密是否正常，正常后再正式对外使用，避免生成错误的公私钥对，阻塞后续业务进度。

```
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;
import org.junit.Test;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.spec.ECGenParameterSpec;
import java.util.HashMap;

public class GenerateSm2KeyPairTest2 {
  public static void main(String[] args) {
    try {
      // 获取sm2公私钥
      String jsonObject = getSm2SecretKey();
      // 可打印生成公私钥信息，例如：log.info(jsonObject);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * 生成SM2的ASN.1格式的公私钥
   *
   * @return SM2的ASN.1格式的公私钥
   */
  public static String getSm2SecretKey() {
    try {
      KeyPair keyPair = generateSm2KeyPair();
      String privateKeyStr = Hex.toHexString(keyPair.getPrivate().getEncoded());
      String publicKeyStr = Hex.toHexString(keyPair.getPublic().getEncoded());
      HashMap<String, String> result = new HashMap<>();
      result.put("Sm2PrivateKey", privateKeyStr);
      result.put("Sm2PublicKey", publicKeyStr);
      return result.toString();
    } catch (Exception e) {
      return null;
    }
  }

  /**
   * SM2算法生成ASN.1格式的公私钥对
   *
   * @return 密钥对信息
   */
  public static KeyPair generateSm2KeyPair() throws Exception {
    try {
      final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
      // 获取一个椭圆曲线类型的密钥对生成器
      final KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
      SecureRandom random = new SecureRandom();
      // 使用SM2的算法区域初始化密钥生成器
      kpg.initialize(sm2Spec, random);
      // 获取密钥对
      KeyPair keyPair = kpg.generateKeyPair();
      return keyPair;
    } catch (Exception e) {
      throw new SecurityException("generateSm2KeyPair failed.");
    }
  }
}
```

#### SM2加密示例代码参考

```
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;

import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;

public class SM2EncTest {
  public static void main(String[] args) {
    encrypt("16进制编码的SM2公钥", "待加密数据");
  }

  public static String encrypt(String pubKey, String data) {
    SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
    if (data == null || data.isEmpty()) {
      return data;
    }
    byte[] in = data.getBytes(StandardCharsets.UTF_8);
    return Hex.toHexString(encrypt(pubKey, in, sm2Engine));
  }

  private static byte[] encrypt(String pubKey, byte[] in, SM2Engine sm2Engine) {
    try {
      byte[] bPubKey = Hex.decode(pubKey);
      byte[] coding = getCoding(bPubKey);
      X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
      ECDomainParameters ecDomainParameters = new ECDomainParameters(x9ECParameters.getCurve(),
              x9ECParameters.getG(), x9ECParameters.getN());
      ECPoint pukPoint = x9ECParameters.getCurve().decodePoint(coding);
      ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, ecDomainParameters);
      sm2Engine.init(true, new ParametersWithRandom(publicKeyParameters, new SecureRandom()));
      return sm2Engine.processBlock(in, 0, in.length);
    } catch (Exception var7) {
      throw new SecurityException(var7);
    }
  }

  private static byte[] getCoding(byte[] publicKey) {
    if (publicKey.length != 64 && publicKey.length != 65) {
      AlgorithmIdentifier aid = new AlgorithmIdentifier(
              X9ObjectIdentifiers.id_ecPublicKey, new ASN1ObjectIdentifier("1.2.156.10197.1.301"));
      SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(publicKey);
      if (!aid.equals(info.getAlgorithm())) {
        throw new SecurityException("encoded not valid");
      } else {
        byte[] coding = info.getPublicKeyData().getBytes();
        if (coding.length != 65) {
          throw new SecurityException("encoded not valid");
        } else {
          return coding;
        }
      }
    } else {
      if (publicKey.length == 64) {
        byte[] bytes = new byte[65];
        bytes[0] = 4;
        System.arraycopy(publicKey, 0, bytes, 1, 64);
        publicKey = bytes;
      }
      return (byte[]) publicKey.clone();
    }
  }
}
```

#### SM2解密示例代码参考

```
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1Set;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.sec.ECPrivateKey;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.util.encoders.Hex;

import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;

public class SM2DecTest {
  public static void main(String[] args) {
    String data = decrypt("16进制编码解密私钥", "密文");
    System.out.println(data);
  }

  private static String decrypt(String priKey, String cipherData) {
    SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
    if (cipherData == null || cipherData.isEmpty()) {
      throw new SecurityException("cipher data is empty when decrypt data");
    }
    if (priKey == null || priKey.isEmpty()) {
      throw new SecurityException("pri key is empty when decrypt data");
    }
    try {
      X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
      ECDomainParameters ecDomainParameters = new ECDomainParameters(x9ECParameters.getCurve(),
              x9ECParameters.getG(), x9ECParameters.getN());
      byte[] bPriKey = Hex.decode(priKey);
      byte[] enContent = Hex.decode(cipherData);
      BigInteger privateKeyD;
      if (bPriKey.length != 32 && bPriKey.length != 33) {
        privateKeyD = getDInt(bPriKey);
      } else {
        privateKeyD = new BigInteger(bPriKey);
      }
      ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, ecDomainParameters);
      sm2Engine.init(false, privateKeyParameters);
      return new String(sm2Engine.processBlock(enContent, 0, enContent.length), StandardCharsets.UTF_8);
    } catch (Exception var7) {
      throw new SecurityException(var7);
    }
  }

  private static BigInteger getDInt(byte[] bytesKey) throws IOException {
    ASN1Sequence sequence = ASN1Sequence.getInstance(bytesKey);
    Enumeration e = sequence.getObjects();
    BigInteger version = ((ASN1Integer) e.nextElement()).getValue();
    if (version.intValue() != 0) {
      throw new IllegalArgumentException("wrong version for private key info");
    }
    AlgorithmIdentifier algId = AlgorithmIdentifier.getInstance(e.nextElement());
    ASN1OctetString privKey = ASN1OctetString.getInstance(e.nextElement());
    ASN1Set attributes;
    if (e.hasMoreElements()) {
      attributes = ASN1Set.getInstance((ASN1TaggedObject) e.nextElement(), false);
    }
    ASN1Primitive primitive = ASN1Primitive.fromByteArray(privKey.getOctets());
    ECPrivateKey privateKey = ECPrivateKey.getInstance(primitive);
    return privateKey.getKey();
  }
}
```