---
title: "API 混合加密 - OceanBase 数据库 V4.3.1 | OceanBase 文档中心"
description: API 混合加密 当 obshell 接收到 HTTP 请求时，将对请求进行安全校验。obshell 的校验内容取决于当前节点的身份，包括但不限于密码、URI、请求体（Request Body）的验证。 使用说明 发送请求 当您需要将 HTTP 请求发送到 obshell 时，需按照以下步骤进行操作。 使用 AES …
---
切换语言

- 中文站 - 简体中文
- International - English
- 日本站 - 日本語

文档反馈![](https://mdn.alipayobjects.com/huamei_22khvb/afts/img/A*P8CuR4UJ_FkAAAAAAAAAAAAADiGDAQ/original) OceanBase 数据库分布式版 - V 4.3.1

# API 混合加密

更新时间：2026-04-10 11:58:01

[编辑](https://github.com/oceanbase/oceanbase-doc/edit/V4.3.1/zh-CN/700.reference/1500.Components-and-Tools/100.manage/100.obshell/400.obshell-api-reference/200.api-hybrid-encryption.md)  

当 obshell 接收到 HTTP 请求时，将对请求进行安全校验。obshell 的校验内容取决于当前节点的身份，包括但不限于密码、URI、请求体（Request Body）的验证。

## 使用说明

### 发送请求

当您需要将 HTTP 请求发送到 obshell 时，需按照以下步骤进行操作。

1. 使用 AES 加密算法对请求体（Request Body）内容加密。

      1. 生成 16 位的随机 AES 密钥（key）和初始化向量（Initialization Vector，IV）。
      2. 使用 CBC 加密模式对序列化后的请求体内容进行加密。
 2. 构造用于安全校验的数据结构 HttpHeader，结构体如下：

   ```Go
   type HttpHeader struct {
       Auth         string  // OceanBase 集群 sys 租户的 root 密码，用于已初始化的集群中节点（或者 MASTER 节点）的安全校验
       Ts           string  // Auth 的有效时间戳
       Uri          string  // 请求的 URI
       Keys         []byte  // 加密 HTTP 请求的 Body 时，使用的 AES 加密算法的 key 和 IV
   }

   ```
 3. 使用 RSA 算法对 HttpHeader 进行加密，并将其设置到 HTTP 请求头部（Request Headers）

      1. 调用 `/api/v1/secret` 接口获得目标节点的公钥（该 API 不需要进行安全校验）
      2. 结合公钥，使用 RSA 算法对序列化后的 HttpHeader 字符串加密，并在加密后进行 Base64 编码，再将其解码成 UTF-8 编码的字符串，得到加密后的字符串 S
      3. 设置 HTTP 请求头部 {"X-OCS-Header": S}

### obshell 校验流程

obshell Server 在接收到 HTTP 请求后会依次执行以下流程进行安全检验。

1. 从请求头部获得键值对 "X-OCS-Header": S。
 2. 结合自身私钥，对 S 进行解密，获得解密后的结构体 HttpHeader。
 3. 检查 HttpHeader 中记录的 Auth 是否正确。
 4. 检查 HttpHeader 中记录的 Uri 与实际请求的 Uri 是否一致。
 5. 结合 HttpHeader 中记录的 Keys 对请求体内容进行解密，得到请求体明文。若解密失败，则验证失败。

只有通过以上安全校验步骤，obshell Server 才会继续对请求进行处理。否则，将向请求方响应安全校验失败。

## Python 发送请求代码

以下为使用 Python 发送请求的代码示例，你只需要修改代码中定义的 `ip`、`port`、`pwd`、`uri` 和 `body`（含义见代码注释）。修改后编译执行该代码，即可得到对应的 HTTP 请求头部和 HTTP 请求体。

#### 说明

执行如下 Python 脚本需使用 3.5 或之后版本的 Python。

```python
import requests as req
import json
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5 as PKCS1_cipher
import base64
from datetime import datetime
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad
aes_key = get_random_bytes(16)
aes_iv = get_random_bytes(16)

max_chunk_size = 53
def encrypt(s, pk):
    key = RSA.import_key(base64.b64decode(pk))
    cipher = PKCS1_cipher.new(key)
    data_to_encrypt = bytes(s.encode('utf8'))
    chunks = [data_to_encrypt[i:i + max_chunk_size] for i in range(0, len(data_to_encrypt), max_chunk_size)]
    encrypted_chunks = [cipher.encrypt(chunk) for chunk in chunks]
    encrypted = b''.join(encrypted_chunks)
    encoded_encrypted_chunks = base64.b64encode(encrypted).decode('utf-8')
    return encoded_encrypted_chunks

def encrypt_body(body, pk):
    cipher = AES.new(aes_key, AES.MODE_CBC, aes_iv)
    return base64.b64encode(cipher.encrypt(pad(bytes(body.encode('utf8')), AES.block_size))).decode('utf8')

class Header:
    auth: str
    ts: str
    uri: str
    keys: bytes
    def __init__(self, auth, ts, uri, keys):
        self.auth = auth
        self.ts = ts
        self.uri = uri
        self.keys = keys

def serialize_struct(struct):
    return json.dumps({
        'auth': struct.auth,
        'ts': struct.ts,
        'uri': struct.uri,
        'keys': base64.b64encode(struct.keys).decode('utf-8')
    })

ip = '10.10.10.1' # 请求节点的 IP
port = 2886 # 请求节点的服务端口
pwd = '******' # OceanBase 集群 sys 租户的 root 密码
uri = '/api/v1/ob/init' # 请求的 URI

body = {} # Request Body 在此更新

# 获取公钥
resp = req.get(f'http://{ip}:{port}/api/v1/secret').text
resp = json.loads(resp)
pk = resp['data']['public_key']
header = Header(pwd, str(int(datetime.now().timestamp()) + 100000), uri, aes_key + aes_iv)

print("request headers: ", 'X-OCS-Header: ' + encrypt(serialize_struct(header), pk))
print("\n")
print("request body: ", encrypt_body(json.dumps(body), pk))
print("\n")
print("encrypt password: ", encrypt(pwd, pk))

```

## Go 发送请求代码

以下为使用 Go 发送请求的代码示例，你只需要修改代码中定义的 `ip`、`port`、`pwd`、`uri` 和 `body`（含义见代码注释）。修改后编译执行该代码，即可得到对应的 HTTP 请求头部和 HTTP 请求体。

```go
package main

import (
    "bytes"
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "crypto/rsa"
    "crypto/x509"
    "encoding/base64"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "time"
)

var (
    ip   = "10.10.10.1"  // 请求节点的 IP
    port = 2886   // 请求节点的服务端口
    uri  = "/api/v1/ob/init"   // 请求的 URI
    pwd  = "******"   // OceanBase 集群 sys 租户的 root 密码
    body = map[string]interface{}{} // Request Body 在此更新
)
var pk string

func main() {
    var err error
    pk, err = getPublicKey(fmt.Sprintf("http://%s:%d/api/v1/secret", ip, port))
    if err != nil {
        fmt.Printf("get public key failed: %s", err.Error())
        return
    }
    fmt.Printf("target agent's public key: %s\n\n", pk)
    encryptedBody, header, err := BuildBodyAndHeader(uri, body)
    if err != nil {
        return
    }
    fmt.Printf("request header: %v\n\nrequest body: %v\n", header, encryptedBody)
}

// getPublicKey function retrieves the public key from the API
func getPublicKey(url string) (string, error) {
    resp, err := http.Get(url)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return "", err
    }

    var response struct {
        Data struct {
            PublicKey string `json:"public_key"`
        } `json:"data"`
    }
    if err = json.Unmarshal(body, &response); err != nil {
        return "", err
    }
    return response.Data.PublicKey, nil
}

func BuildBodyAndHeader(uri string, param interface{}) (encryptedBody interface{}, header map[string]string, err error) {
    encryptedBody, Key, Iv, err := EncryptBodyWithAes(param)
    if err != nil {
        return nil, nil, err
    }

    header = BuildHeader(Key, Iv)
    return encryptedBody, header, nil
}

type HttpHeader struct {
    Auth  string
    Ts    string
    Uri   string
    Keys  []byte
}

func BuildHeader(keys ...[]byte) map[string]string {
    headers := make(map[string]string)

    var aesKeys []byte
    if len(keys) != 2 {
        aesKeys = nil
    } else {
        aesKeys = append(keys[0], keys[1]...)
    }
    header := HttpHeader{
        Auth: pwd,
        Ts:   fmt.Sprintf("%d", time.Now().Add(100*time.Second).Unix()),
        Uri:  uri,
        Keys: aesKeys,
    }
    mAuth, err := json.Marshal(header)
    if err != nil {
        return nil
    }
    encrypt, err := RSAEncrypt(mAuth, pk)
    if err != nil {
        return nil
    }
    headers["X-OCS-Header"] = encrypt
    return headers
}

func RSAEncrypt(raw []byte, pk string) (string, error) {
    pkix, err := base64.StdEncoding.DecodeString(pk)
    if err != nil {
        return "", err
    }
    pub, err := x509.ParsePKCS1PublicKey(pkix)
    if err != nil {
        return "", err
    }
    if len(raw) == 0 {
        b, err := rsa.EncryptPKCS1v15(rand.Reader, pub, raw)
        return base64.StdEncoding.EncodeToString(b), err
    }
    // 分段加密
    blockSize := 512/8 - 11
    numBlocks := (len(raw) + blockSize - 1) / blockSize
    ciphertext := make([]byte, 0)
    for i := 0; i < numBlocks; i++ {
        start := i * blockSize
        end := (i + 1) * blockSize
        if end > len(raw) {
            end = len(raw)
        }
        encrypted, err := rsa.EncryptPKCS1v15(rand.Reader, pub, raw[start:end])
        if err != nil {
            return "", err
        }
        ciphertext = append(ciphertext, encrypted...)
    }
    return base64.StdEncoding.EncodeToString(ciphertext), err
}

func EncryptBodyWithAes(body interface{}) (encryptedBody interface{}, key []byte, iv []byte, err error) {
    if body == nil {
        return
    }
    mBody, err := json.Marshal(body)
    if err != nil {
        return
    }
    key = make([]byte, 16)
    iv = make([]byte, 16) // equal to block_size，16 bytes
    _, err = rand.Read(key)
    if err != nil {
        return
    }
    _, err = rand.Read(iv)
    if err != nil {
        return
    }
    encryptedBody, err = AESEncrypt(mBody, key, iv)
    return
}

func AESEncrypt(raw []byte, key []byte, iv []byte) (string, error) {
    // 创建 AES 加密器
    block, err := aes.NewCipher(key)
    if err != nil {
        return "", err
    }
    raw = pkcs5Padding(raw, block.BlockSize())
    // 创建 AES 的 CBC 模式加密器
    mode := cipher.NewCBCEncrypter(block, iv)

    // 加密数据
    ciphertext := make([]byte, len(raw))
    mode.CryptBlocks(ciphertext, raw)

    return base64.StdEncoding.EncodeToString(ciphertext), err
}

func pkcs5Padding(ciphertext []byte, blockSize int) []byte {
    padding := blockSize - len(ciphertext)%blockSize
    padtext := bytes.Repeat([]byte{byte(padding)}, padding)
    return append(ciphertext, padtext...)
}

```

## Java 示例代码

以下为使用 Java 发送请求的代码示例，你只需要修改代码中定义的 `agent`、`pwd`、`uri` 和 `body`（含义见代码注释）。修改后编译执行该代码，即可得到对应的 HTTP 请求头部和 HTTP 请求体。

```java
package com.oceanbase;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.RSAPublicKeySpec;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.net.URL;

import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Sequence;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;

import com.google.gson.Gson;

public class Main {
    /* 在这里修改密码、uri 和目标 agent */
    private static final String pwd = "******";  //OceanBase 集群 sys 租户的 root 密码
    private static final String uri = "/api/v1/obcluster/config"; // 请求的 URI
    private static final String agent = "10.10.10.1:2886";  // 请求节点的 IP 和端口

    private static final String RSA_ALGORITHM = "RSA";
    private static final Integer RSA_KEY_SIZE = 512;
    private static final String RSA_TRANSFORMATION = "RSA/ECB/PKCS1Padding";
    private static final String AES_ALGORITHM = "AES";
    private static final Integer AES_KEY_SIZE = 128;
    private static final String AES_TRANSFORMATION = "AES/CBC/PKCS5Padding";

    public static class HTTPHeader implements Serializable {
        private static final long serialVersionUID = 1L;
        public String auth;
        public String ts;
        public String uri;
        private String keys;

        public HTTPHeader(String pwd, int timestamp, String uri, byte[] keys) {
            this.auth = pwd;
            this.ts = String.valueOf(timestamp);
            this.uri = uri;
            this.keys = Base64.getEncoder().encodeToString(keys);
        }

    }

    public static String serializeStruct(HTTPHeader header) {
        Gson gson = new Gson();

        // 将结构转化为 JSON 并对 keys 字段进行 Base64 编码
        return gson.toJson(header);
    }

    public static String encryptRSAWithSegment(Cipher cipher, byte[] data) throws Exception {
        int inputLen = data.length;
        int maxSize = RSA_KEY_SIZE / 8 - 11; // 数据长度减去加密填充
        int offSet = 0;
        byte[] cache;
        int i = 0;
        List<byte[]> encryptedSegments = new ArrayList<>();

        // 对数据分段加密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > maxSize) {
                cache = cipher.doFinal(data, offSet, maxSize);
            } else {
                cache = cipher.doFinal(data, offSet, inputLen - offSet);
            }
            encryptedSegments.add(cache);
            i++;
            offSet = i * maxSize;
        }

        // 合并加密的段
        int totalLen = encryptedSegments.stream().mapToInt(segment -> segment.length).sum();
        byte[] encryptedData = new byte[totalLen];
        int currentIndex = 0;
        for (byte[] segment : encryptedSegments) {
            System.arraycopy(segment, 0, encryptedData, currentIndex, segment.length);
            currentIndex += segment.length;
        }

        return Base64.getEncoder().encodeToString(encryptedData);
    }

    private static String getPublicKey(String urlStr) throws Exception {
        URL url = new URL(urlStr);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");

        int responseCode = conn.getResponseCode();
        if (responseCode == HttpURLConnection.HTTP_OK) {
            try (BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()))) {
                String inputLine;
                StringBuilder response = new StringBuilder();

                while ((inputLine = in.readLine()) != null) {
                    response.append(inputLine);
                }

                System.out.println("Response: " + response.toString());
                JSONObject jsonResponse = JSON.parseObject(response.toString());
                System.out.println("Public Key: " + jsonResponse.getJSONObject("data").getString("public_key"));
                return jsonResponse.getJSONObject("data").getString("public_key");
            } catch (Exception e){
                System.out.println("Failed to read response: " + e.getMessage());
                throw new Exception("Failed to read response", e);
            }
        } else {
            throw new Exception("HTTP request failed with code " + responseCode);
        }
    }

    private static PublicKey convertPKCS1ToPublicKey(String pkcs1PublicKeyStr) throws Exception {
        pkcs1PublicKeyStr = pkcs1PublicKeyStr.replaceAll("\\n", "")
                .replace("-----BEGIN RSA PUBLIC KEY-----", "")
                .replace("-----END RSA PUBLIC KEY-----", "");
        byte[] pkcs1PublicKey = Base64.getDecoder().decode(pkcs1PublicKeyStr);

        ASN1InputStream asn1InputStream = new ASN1InputStream(pkcs1PublicKey);
        ASN1Sequence sequence = (ASN1Sequence) asn1InputStream.readObject();
        BigInteger modulus = ((ASN1Integer) sequence.getObjectAt(0)).getValue();
        BigInteger publicExponent = ((ASN1Integer) sequence.getObjectAt(1)).getValue();
        asn1InputStream.close();

        RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(modulus, publicExponent);

        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePublic(publicKeySpec);
    }

    public static void main(String[] args) throws Exception {
        // 获取公钥
        String publicKeyStr = getPublicKey("http://" +agent+ "/api/v1/secret");
        PublicKey publicKey = convertPKCS1ToPublicKey(publicKeyStr);

        // 初始化 RSA 和 AES 密钥
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance(RSA_ALGORITHM);
        keyGen.initialize(RSA_KEY_SIZE); // RSA 2048 位密钥

        KeyGenerator aesKeyGen = KeyGenerator.getInstance(AES_ALGORITHM);
        aesKeyGen.init(AES_KEY_SIZE); // AES 128 位密钥
        SecretKey aesKey = aesKeyGen.generateKey();
        byte[] aesKeyBytes = aesKey.getEncoded();
        byte[] iv = new byte[16]; // 初始化向量为 16 字节
        SecureRandom random = new SecureRandom();
        random.nextBytes(iv);

        // RSA 分段加密数据
        Cipher rsaCipher = Cipher.getInstance(RSA_TRANSFORMATION);
        rsaCipher.init(Cipher.ENCRYPT_MODE, publicKey);

        // 准备 Header 中要存放的数据 (URI, 时间戳, AES 密钥和 IV)
        // 将数据以 Base64 形式编码，然后拼接成一个字符串
        long timestamp = System.currentTimeMillis() / 1000 + 100000;
        HTTPHeader header = new HTTPHeader(pwd, (int) timestamp, uri, concatenate(aesKeyBytes, iv));
        System.out.println("Header: " +  serializeStruct(header));
        String encryptedHeader = encryptRSAWithSegment(rsaCipher, serializeStruct(header).getBytes(StandardCharsets.UTF_8));

        // AES 加密 HTTP 数据体
        Cipher aesCipher = Cipher.getInstance(AES_TRANSFORMATION);
        aesCipher.init(Cipher.ENCRYPT_MODE, aesKey, new IvParameterSpec(iv));

        /* 在这里构建 Request Body */
        JSONObject databody = new JSONObject();
        data.body.put("clusterId", 1);
        data.body.put("clusterName", "21421");
        data.body.put("rootPwd", "2145325");
        System.out.println(data.body.toString());
        byte[] encryptedBody = aesCipher.doFinal(data.body.toString().getBytes(StandardCharsets.UTF_8));
        String encryptedBodyBase64 = Base64.getEncoder().encodeToString(encryptedBody);

        // 输出结果
        System.out.println("Encrypted Header (Base64, RSA-Encrypted): " + encryptedHeader);
        System.out.println("Encrypted Body (Base64, AES-Encrypted): " + encryptedBodyBase64);
    }

    private static byte[] concatenate(byte[]... arrays) {
        int totalLength = 0;
        for (byte[] array : arrays) {
            totalLength += array.length;
        }
        byte[] result = new byte[totalLength];
        int currentIndex = 0;
        for (byte[] array : arrays) {
            System.arraycopy(array, 0, result, currentIndex, array.length);
            currentIndex += array.length;
        }
        return result;
    }
}

```

 上一篇 下一篇 ![有帮助](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*y6ocSqN8cqsAAAAAAAAAAAAAARQnAQ)![无帮助](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*BG9IQJyLHF8AAAAAAAAAAAAAARQnAQ)![反馈](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*eTWdQKCRKHwAAAAAAAAAAAAAARQnAQ)[AI](https://www.oceanbase.com/obi) 咨询热线
