> ## Documentation Index
> Fetch the complete documentation index at: https://doc.haipay.net/llms.txt
> Use this file to discover all available pages before exploring further.

# RSA密钥生成

export const KeyForm = ({infoText, privateKeyLabel, publicKeyLabel, privateKeyPlaceholder = "Private Key", publicKeyPlaceholder = "Public Key", copyLabel = "Copy", copiedLabel = "Copied", generateButtonLabel = "Generate Keys"}) => {
  const [privateKey, setPrivateKey] = useState("");
  const [publicKey, setPublicKey] = useState("");
  const [copiedPrivate, setCopiedPrivate] = useState(false);
  const [copiedPublic, setCopiedPublic] = useState(false);
  const arrayBufferToBase64 = buffer => {
    let binary = "";
    const bytes = new Uint8Array(buffer);
    const len = bytes.byteLength;
    for (let i = 0; i < len; i++) {
      binary += String.fromCharCode(bytes[i]);
    }
    return btoa(binary);
  };
  const generateKeys = async () => {
    const keyPair = await window.crypto.subtle.generateKey({
      name: "RSASSA-PKCS1-v1_5",
      modulusLength: 2048,
      publicExponent: new Uint8Array([1, 0, 1]),
      hash: "SHA-256"
    }, true, ["sign", "verify"]);
    const pkcs8 = await window.crypto.subtle.exportKey("pkcs8", keyPair.privateKey);
    const spki = await window.crypto.subtle.exportKey("spki", keyPair.publicKey);
    setPrivateKey(arrayBufferToBase64(pkcs8));
    setPublicKey(arrayBufferToBase64(spki));
  };
  const copyToClipboard = async (text, setCopied) => {
    if (!text) return;
    const markCopied = () => {
      setCopied(true);
      setTimeout(() => setCopied(false), 1500);
    };
    try {
      if (navigator.clipboard && navigator.clipboard.writeText) {
        await navigator.clipboard.writeText(text);
        markCopied();
        return;
      }
    } catch (error) {}
    try {
      const el = document.createElement("textarea");
      el.value = text;
      el.setAttribute("readonly", "");
      el.style.position = "fixed";
      el.style.top = "0";
      el.style.left = "0";
      el.style.opacity = "0";
      document.body.appendChild(el);
      el.focus();
      el.select();
      const ok = document.execCommand("copy");
      document.body.removeChild(el);
      if (ok) markCopied();
    } catch (error) {}
  };
  const InfoIcon = () => <svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" style={{
    flexShrink: 0,
    display: "block"
  }}>
            <path d="M26.1807 23.8255C25.0271 24.7956 24.1941 25.4954 24.1941 26.5305L24.1957 27.4712H21.2559V19.4915L24.4161 16.2964C24.8661 15.8412 24.863 15.1057 24.4107 14.6535C23.9576 14.2029 23.227 14.2037 22.7755 14.6589L20.1 17.364L17.0446 14.2742C16.5938 13.8197 15.8625 13.8174 15.4087 14.2688C14.9571 14.7209 14.954 15.4564 15.404 15.9116L18.9441 19.4915V27.4712H16.0066V26.5305C16.0066 25.4273 15.0889 24.6546 13.9276 23.6761C11.9795 22.0355 9.31176 19.7888 9.31176 15.3356C9.31176 9.26277 14.1518 4.3226 20.1 4.3226C26.0489 4.3226 30.8882 9.26277 30.8882 15.3356C30.8882 19.8678 28.0556 22.2492 26.1807 23.8255ZM22.4888 32.1938H17.7119C16.7965 32.1938 16.0482 31.4506 16.0036 30.5192C16.0059 30.4906 16.0066 30.4619 16.0066 30.4325V29.7938H24.1988L24.2003 30.4325C24.2003 31.4033 23.4328 32.1938 22.4888 32.1938ZM20.1 2C12.8765 2 7 7.98224 7 15.3356C7 20.8727 10.4068 23.7419 12.4419 25.4559C12.9081 25.8485 13.5369 26.378 13.6949 26.6289V28.3383C13.6702 28.4327 13.6525 28.5303 13.6525 28.6325C13.6525 28.7347 13.6702 28.8322 13.6949 28.9267V30.321C13.691 30.3582 13.6895 30.3946 13.6895 30.4325C13.6895 32.6839 15.4942 34.5164 17.7119 34.5164H22.4888C24.7073 34.5164 26.5121 32.6839 26.5121 30.4325L26.5098 28.9174C26.5329 28.826 26.5491 28.7316 26.5491 28.6325C26.5491 28.5326 26.5321 28.4374 26.5082 28.3445L26.5059 26.6699C26.6777 26.4361 27.221 25.9793 27.6649 25.6069C29.7347 23.8657 33.2 20.9524 33.2 15.3356C33.2 7.98224 27.3235 2 20.1 2ZM25.4944 35.6774H14.7062C14.0681 35.6774 13.5503 36.1977 13.5503 36.8387C13.5503 37.4797 14.0681 38 14.7062 38H25.4944C26.1325 38 26.6503 37.4797 26.6503 36.8387C26.6503 36.1977 26.1325 35.6774 25.4944 35.6774Z" fill="#0BCACA" />
        </svg>;
  const CopyIcon = () => <svg className="rsa-copy-icon" viewBox="0 0 16 20" fill="none" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet" aria-hidden="true" style={{
    display: "block",
    width: "24px",
    height: "24px",
    minWidth: "24px",
    minHeight: "24px",
    maxWidth: "24px",
    maxHeight: "24px",
    pointerEvents: "none"
  }}>
            <path d="M11.4627 5.78313C11.4627 5.11773 10.9281 4.57831 10.2687 4.57831H2.62687C1.96742 4.57831 1.43284 5.11773 1.43284 5.78313V17.3494C1.43284 18.0148 1.96742 18.5542 2.62687 18.5542H10.2687C10.9281 18.5542 11.4627 18.0148 11.4627 17.3494V5.78313ZM12.8955 17.3494C12.8955 18.8133 11.7194 20 10.2687 20H2.62687C1.17609 20 0 18.8133 0 17.3494V5.78313C0 4.31925 1.17609 3.13253 2.62687 3.13253H10.2687C11.7194 3.13253 12.8955 4.31925 12.8955 5.78313V17.3494Z" fill="#2B2B2B" />
            <path d="M14.5672 14.6988V2.6506C14.5672 1.9852 14.0326 1.44578 13.3731 1.44578H4.29851C3.90284 1.44578 3.58209 1.12213 3.58209 0.722892C3.58209 0.32365 3.90284 0 4.29851 0H13.3731C14.8239 0 16 1.18672 16 2.6506V14.6988C16 15.098 15.6792 15.4217 15.2836 15.4217C14.8879 15.4217 14.5672 15.098 14.5672 14.6988Z" fill="#2B2B2B" />
        </svg>;
  const CopyButton = ({copied, onCopy, disabled}) => <button type="button" className="rsa-copy-btn" onClick={e => {
    e.preventDefault();
    e.stopPropagation();
    onCopy();
  }} disabled={disabled} aria-label={copied ? copiedLabel : copyLabel} style={{
    flexShrink: 0,
    alignSelf: "flex-start",
    position: "relative",
    zIndex: 2,
    display: "inline-flex",
    flexDirection: "column",
    alignItems: "center",
    gap: "2px",
    padding: 0,
    border: "none",
    background: "transparent",
    cursor: disabled ? "default" : "pointer",
    opacity: disabled ? 0.5 : 1,
    boxSizing: "border-box"
  }}>
            <CopyIcon />
            {copied && <span style={{
    fontSize: "12px",
    lineHeight: "16px",
    color: "#7A49E9",
    whiteSpace: "nowrap",
    fontFamily: "Inter, sans-serif"
  }}>
                    {copiedLabel}
                </span>}
        </button>;
  const KeyField = ({label, value, placeholder, copied, onCopy}) => <div style={{
    display: "flex",
    flexDirection: "column",
    gap: 4,
    width: "100%"
  }}>
            <div style={{
    height: 30,
    display: "flex",
    alignItems: "center",
    fontSize: 14,
    color: "#101828",
    lineHeight: "normal"
  }}>
                {label}
            </div>
            <div style={{
    backgroundColor: "#FFFFFF",
    border: "1px solid #D9D9D9",
    borderRadius: 8,
    height: 180,
    boxSizing: "border-box",
    padding: "10px 12px",
    display: "flex",
    gap: 10,
    alignItems: "flex-start",
    overflow: "hidden"
  }}>
                <textarea value={value} readOnly placeholder={placeholder} className="rsa-key-textarea" style={{
    flex: 1,
    minWidth: 0,
    height: "100%",
    margin: 0,
    padding: 0,
    border: "none",
    outline: "none",
    resize: "none",
    background: "transparent",
    fontFamily: "Inter, sans-serif",
    fontSize: 14,
    lineHeight: "20px",
    color: "#101828",
    wordBreak: "break-all"
  }} />
                <CopyButton copied={copied} onCopy={onCopy} disabled={!value} />
            </div>
        </div>;
  return <div className="rsa-key-form not-prose" style={{
    display: "flex",
    flexDirection: "column",
    gap: 20,
    width: "100%",
    color: "#2B2B2B"
  }}>
            <div style={{
    display: "flex",
    gap: 20,
    alignItems: "center",
    padding: "20px 0"
  }}>
                <InfoIcon />
                <p style={{
    margin: 0,
    flex: 1,
    fontSize: 20,
    lineHeight: "normal",
    color: "#000000",
    letterSpacing: "-0.2px"
  }}>
                    {infoText}
                </p>
            </div>

            <div style={{
    display: "flex",
    flexDirection: "column",
    gap: 10,
    width: "100%"
  }}>
                <KeyField label={privateKeyLabel} value={privateKey} placeholder={privateKeyPlaceholder} copied={copiedPrivate} onCopy={() => copyToClipboard(privateKey, setCopiedPrivate)} />
                <KeyField label={publicKeyLabel} value={publicKey} placeholder={publicKeyPlaceholder} copied={copiedPublic} onCopy={() => copyToClipboard(publicKey, setCopiedPublic)} />
                <button type="button" onClick={generateKeys} style={{
    alignSelf: "flex-start",
    display: "inline-flex",
    flexDirection: "row",
    alignItems: "center",
    justifyContent: "center",
    gap: "12px",
    boxSizing: "border-box",
    width: "auto",
    minWidth: "88px",
    height: "48px",
    padding: "12px 16px",
    borderRadius: "8px",
    border: "none",
    backgroundColor: "#7A49E9",
    color: "#FFFFFF",
    fontFamily: "Inter, sans-serif",
    fontSize: "14px",
    fontWeight: 500,
    lineHeight: "24px",
    whiteSpace: "nowrap",
    cursor: "pointer"
  }}>
                    {generateButtonLabel}
                </button>
            </div>
        </div>;
};

<KeyForm infoText="使用JavaScript生成，不会向服务器发送任何请求；私钥请妥善保存，公钥在haipay服务端输入即可。" privateKeyLabel="私钥（请妥善保管）" publicKeyLabel="公钥（可对外共享）" privateKeyPlaceholder="Private Key" publicKeyPlaceholder="Public Key" copyLabel="复制" copiedLabel="已复制" generateButtonLabel="生成密钥" />

## 相关主题

* [HaiPay 配置与签名指南](/docs/zh/guide/config_settings_and_signature_rules_guide)
* [HaiPay 联调环境与请求地址](/docs/zh/guide/api_parameters_doc)
* [HaiPay 集成步骤指南](/docs/zh/guide/integration_guide)
