iOS开发——openssl生成CSR (pkcs10) 请求证书

由于涉及在线开户相关,所以整理一下~

 

  本文在前人的基础上更加完善:通过openssl 生成证书请求CSR  符合pkcs10的语法规范

  为客户端生成,代码为C语言,可以直接在xcode的中于objective-c混合开发。

  主要概念:

    OpenSSL:一个安全套接字层密码库,囊括主要的密码算法、常用的密钥和证书封装管理功能及SSL协议,并提供丰富的应用程序供测试或其它目的使用。

    CSR:证书请求 ,Cerificate Signing Request的英文缩写我们最终想要的结果,为base64转码格式

   privateKey : 私钥 (也是王我们想要的,W为base64转码格式,以便备用验证等)

   pkcs#10:PKCS 全称是 Public-Key Cryptography Standards ,是由 RSA 实验室与其它安全系统开发商为促进公钥密码的发展而制订的一系列标准。pkcs10 本质是证书请求语法标准。PKCS#10定义了证书请求的语法。证书请求包含了一个唯一识别名、公钥和可选的一组属性,它们一起被请求证书的实体签名(证书管理协议中的PKIX证书请求消息就是一个PKCS#10)。

 

代码文件我已上传到Github:  https://github.com/liuchengli/CSR-

直接下载代码运查阅或者直接运行代码看看,效果更好

 

 

 

如果我们想要通过代码方式实现证书请求,就要需要用到openssl的函数。 
1、因为openssl库是基于C语言的库,为方便在Xcode中使用,我们需要把它编译为静态库。具体如何编译,可以参考这篇文件:在iOS中如何编译openssl的静态库

2、如果不想这么麻烦,大家可以在这里下载已经编译好的.a文件。感谢这些前辈们帮我们省了不少事。libssl.a支持多架构

3、第二步下载下来的文件目录结构是这样的: 
a)include目录下包含一个openssl目录,openssl目录底下是所有头文件 
b)libssl.a和libcrypto.a 
4、把上面所有文件拖到工程中,
注意:在TARGET->您的项目名称->Build Settings 里面搜索:Header Search Path,在里面增加配置:$(PROJECT_DIR)/include/ 

 

新建一个C File,命名为:header,Xcode会同时帮我们生成一个header.h的头文件。 

在header.h中,我们引用一下头文件,并且声明需要用到的函数.

 

#include <openssl/x509.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <string.h>
//**在这里需要声明函数**/
// 传入的信息参数,字符串形式一次传入,不传也可以
X509_NAME *parse_name(char *subject, long chtype, int multirdn);
// 传入信息参数,分内容对应传入
X509_NAME *CreateDN(char *pbEmail, char *pbCN, char *pbOU, char *pbO, char *pbL, char *pbST, char *pbC);
// 获取证书请求CSR和私钥

 

 

 

 

 

 

在header.c中,实现刚才声明的三个函数,并实现

 

 

 

//  Created by 刘成利 on 2017/6/1.
//  Copyright © 2017年 刘成利. All rights reserved.
//

#include "pkcs10header.h"


/*
 * subject is expected to be in the format /type0=value0/type1=value1/type2=...
 * where characters may be escaped by \
 */
X509_NAME *parse_name(char *subject, long chtype, int multirdn)
{
    size_t buflen = strlen(subject)+1; /* to copy the types and values into. due to escaping, the copy can only become shorter */
    char *buf = OPENSSL_malloc(buflen);
    size_t max_ne = buflen / 2 + 1; /* maximum number of name elements */
    char **ne_types = OPENSSL_malloc(max_ne * sizeof (char *));
    char **ne_values = OPENSSL_malloc(max_ne * sizeof (char *));
    int *mval = OPENSSL_malloc (max_ne * sizeof (int));
    
    char *sp = subject, *bp = buf;
    int i, ne_num = 0;
    
    X509_NAME *n = NULL;
    int nid;
    
    if (!buf || !ne_types || !ne_values || !mval)
    {
        //BIO_printf(bio_err, "malloc error\n");
        goto error;
    }
    
    if (*subject != '/')
    {
        //BIO_printf(bio_err, "Subject does not start with '/'.\n");
        goto error;
    }
    sp++; /* skip leading / */
    
    /* no multivalued RDN by default */
    mval[ne_num] = 0;
    
    while (*sp)
    {
        /* collect type */
        ne_types[ne_num] = bp;
        while (*sp)
        {
            if (*sp == '\\') /* is there anything to escape in the type...? */
            {
                if (*++sp)
                    *bp++ = *sp++;
                else
                {
                    //BIO_printf(bio_err, "escape character at end of string\n");
                    goto error;
                }
            }
            else if (*sp == '=')
            {
                sp++;
                *bp++ = '\0';
                break;
            }
            else
                *bp++ = *sp++;
        }
        if (!*sp)
        {
            //BIO_printf(bio_err, "end of string encountered while processing type of subject name element #%d\n", ne_num);
            goto error;
        }
        ne_values[ne_num] = bp;
        while (*sp)
        {
            if (*sp == '\\')
            {
                if (*++sp)
                    *bp++ = *sp++;
                else
                {
                    //BIO_printf(bio_err, "escape character at end of string\n");
                    goto error;
                }
            }
            else if (*sp == '/')
            {
                sp++;
                /* no multivalued RDN by default */
                mval[ne_num+1] = 0;
                break;
            }
            else if (*sp == '+' && multirdn)
            {
                /* a not escaped + signals a mutlivalued RDN */
                sp++;
                mval[ne_num+1] = -1;
                break;
            }
            else
                *bp++ = *sp++;
        }
        *bp++ = '\0';
        ne_num++;
    }
    
    if (!(n = X509_NAME_new()))
        goto error;
    
    for (i = 0; i < ne_num; i++)
    {
        if ((nid=OBJ_txt2nid(ne_types[i])) == NID_undef)
        {
            //BIO_printf(bio_err, "Subject Attribute %s has no known NID, skipped\n", ne_types[i]);
            continue;
        }
        
        if (!*ne_values[i])
        {
            //BIO_printf(bio_err, "No value provided for Subject Attribute %s, skipped\n", ne_types[i]);
            continue;
        }
        
        if (!X509_NAME_add_entry_by_NID(n, nid, chtype, (unsigned char*)ne_values[i], -1,-1,mval[i]))
            goto error;
    }
    
    OPENSSL_free(ne_values);
    OPENSSL_free(ne_types);
    OPENSSL_free(buf);
    OPENSSL_free(mval);
    return n;
    
error:
    X509_NAME_free(n);
    if (ne_values)
        OPENSSL_free(ne_values);
    if (ne_types)
        OPENSSL_free(ne_types);
    if (mval)
        OPENSSL_free(mval);
    if (buf)
        OPENSSL_free(buf);
    return NULL;
}


X509_NAME *CreateDN(char *pbEmail, char *pbCN, char *pbOU, char *pbO, char *pbL, char *pbST, char *pbC)
{
    X509_NAME *pX509Name = NULL;
    if(pbCN == NULL)
    {
        return NULL;
    }
    
    if (!(pX509Name = X509_NAME_new()))
    {
        return NULL;
    }
    X509_NAME_add_entry_by_txt(pX509Name, "emailAddress", V_ASN1_UTF8STRING, pbEmail, -1, -1, 0);
    X509_NAME_add_entry_by_txt(pX509Name, "CN", V_ASN1_UTF8STRING, pbCN, -1, -1, 0);
    X509_NAME_add_entry_by_txt(pX509Name, "OU", V_ASN1_UTF8STRING, pbOU, -1, -1, 0);
    X509_NAME_add_entry_by_txt(pX509Name, "O", V_ASN1_UTF8STRING, pbO, -1, -1, 0);
    X509_NAME_add_entry_by_txt(pX509Name, "L", V_ASN1_UTF8STRING, pbL, -1, -1, 0);
    X509_NAME_add_entry_by_txt(pX509Name, "ST", V_ASN1_UTF8STRING, pbST, -1, -1, 0);
    X509_NAME_add_entry_by_txt(pX509Name, "C", V_ASN1_UTF8STRING, pbC, -1, -1, 0);
    return pX509Name;
}

long int GenCSR(char *pbDN, int nDNLen, char *pCSR, size_t nCSRSize, char *privateKey)
{
    char szAltName[] = "DNS:www.aaaa.com";
    char szComment[] = "Create by liuchengli";
    char szKeyUsage[] = "digitalSignature, nonRepudiation";
    char szExKeyUsage[] = "serverAuth, clientAuth";
    
    X509_REQ        *pX509Req = NULL;
    int             iRV = 0;
    long            lVer = 3;
    X509_NAME       *pX509DN = NULL;
    EVP_PKEY        *pEVPKey = NULL;
    RSA             *pRSA = NULL;
    X509_NAME_ENTRY *pX509Entry = NULL;
    char            szBuf[255] = {0};
    unsigned char   mdout[20];
    unsigned int    nLen, nModLen;
    int             bits = 2048;
    unsigned long   E = RSA_3;
    unsigned char   *pDer = NULL;
    unsigned char   *p = NULL;
    FILE            *fp = NULL;
    const EVP_MD    *md = NULL;
    X509            *pX509 = NULL;
    BIO             *pBIO = NULL;
    BIO             *pPemBIO = NULL;
    BUF_MEM         *pBMem = NULL;
    
    //STACK_OF(X509_EXTENSION) *pX509Ext;
    
    if(pbDN == NULL)
    {
        return -1;
    }
    
    
    pX509DN = parse_name(pbDN, V_ASN1_UTF8STRING, 0);
    
    pX509Req = X509_REQ_new();
    
    iRV = X509_REQ_set_version(pX509Req, lVer);
    
    
    iRV = X509_REQ_set_subject_name(pX509Req, pX509DN);
    
    
   
    // 签名方式为哈希(非MD5)
    md  = EVP_sha1();
   
    iRV = X509_REQ_digest(pX509Req, md, mdout, &nModLen);
     */
    

    /* pub key */
  
    pEVPKey = EVP_PKEY_new();
    
    pRSA = RSA_generate_key(bits, E, NULL, NULL);
   
    EVP_PKEY_assign_RSA(pEVPKey, pRSA);
   
    iRV = X509_REQ_set_pubkey(pX509Req, pEVPKey);
    
//    EVP_PKEY *PEM_read_bio_PrivateKey(BIO *bp, EVP_PKEY **pEVPKey, pem_password_cb *cb, void *u);
    
    
    
    /* attribute */
    strcpy(szBuf,szAltName);
    nLen = strlen(szBuf);
    iRV = X509_REQ_add1_attr_by_txt(pX509Req, "subjectAltName", V_ASN1_UTF8STRING, szBuf, nLen);
    
    strcpy(szBuf, szKeyUsage);
    nLen = strlen(szBuf);
    iRV = X509_REQ_add1_attr_by_txt(pX509Req, "keyUsage", V_ASN1_UTF8STRING, szBuf, nLen);
    
    
    strcpy(szBuf, szExKeyUsage);
    nLen = strlen(szBuf);
    iRV = X509_REQ_add1_attr_by_txt(pX509Req, "extendedKeyUsage", V_ASN1_UTF8STRING, szBuf, nLen);
    
    
    strcpy(szBuf, szComment);
    nLen = strlen(szBuf);
    iRV = X509_REQ_add1_attr_by_txt(pX509Req, "nsComment", V_ASN1_UTF8STRING, szBuf, nLen);
    
    
   
    md  = EVP_sha1();
 
    iRV = X509_REQ_digest(pX509Req, md, mdout, &nModLen);
    
//    printf("摘要:\n");
    for (int i = 0; i < strlen(mdout); i++) {
        
//        printf("%x",mdout[i]);

    }
    
   
    iRV = X509_REQ_sign(pX509Req, pEVPKey, md);
    if(!iRV)
    {
        printf("sign err!\n");
        X509_REQ_free(pX509Req);
        return -1;
    }
    
    
//    unsigned char *signChart = pX509Req->signature->data;
//    printf("\n签名:%s\n",signChart);
//    char *skey;
//    int n;
//    pPemBIO = BIO_new(BIO_s_mem());
//    BIO_read(pPemBIO, skey, n);
//    printf("skey:%s",skey);
    
    // 写入文件PEM格式
    //  pBIO = BIO_new_file("certreq.txt", "w");
    //  PEM_write_bio_X509_REQ(pBIO, pX509Req, NULL, NULL);
    //  BIO_free(pBIO);
    
    // 2.1  返回PEM字符  PKCS10证书请求
    pPemBIO = BIO_new(BIO_s_mem());
    //    PEM_write_bio_X509_REQ(pPemBIO, pX509Req, NULL, NULL);
    PEM_write_bio_X509_REQ(pPemBIO, pX509Req);
    BIO_get_mem_ptr(pPemBIO,&pBMem);
    if(pBMem->length <= nCSRSize)
    {
        memcpy(pCSR, pBMem->data, pBMem->length);
    }
    BIO_free(pPemBIO);
    
    
    


    
   
    char priKey[2048] = {0};
    char passwd[] = "123";  // 对私钥进行加密的密码
    pPemBIO = BIO_new(BIO_s_mem());
//    if (PEM_write_bio_RSAPrivateKey(pPemBIO, pRSA, EVP_des_ede3(), (unsigned char *)passwd, 4, NULL, NULL)!=1) {
//        printf("private key error\n");
//    }
    if (PEM_write_bio_RSAPrivateKey(pPemBIO, pRSA,NULL, NULL, 0, NULL, NULL)!=1) {
        printf("private key error\n");
    }

   
    BIO_get_mem_ptr(pPemBIO,&pBMem);
    if(pBMem->length <= nCSRSize)
    {
        memcpy(privateKey, pBMem->data, pBMem->length);
    }

    BIO_free(pPemBIO);
    
//    printf("私钥:\n%s\n",privateKey);
    
    /* DER编码 */
    //nLen = i2d_X509_REQ(pX509Req, NULL);
    //pDer = (unsigned char *)malloc(nLen);
    //p = pDer;
    //nLen = i2d_X509_REQ(pX509Req, &p);
    //free(pDer);
    
    //  验证CSR
    OpenSSL_add_all_algorithms();
  
    iRV = X509_REQ_verify(pX509Req, pEVPKey);
    if(iRV<0)
    {
        printf("verify err.\n");
    }
    
    X509_REQ_free(pX509Req);
    
    
    // 输出pkcs10CSR证书请求
//    printf("CSR:\n%s", pCSR);
    
    
 
    
    
    return nCSRSize;
}

 

 

 

 

最后在OC中代码调用方式:

   /* 生成pkcs10 证书请求  格式:/CN=参数1/O=参数2/OU=参数3……
     * 例如:"/CN=www.aaaa.com/O=aaaa.com/OU=IT/ST=Beijing City/L=beijing/C=CN/emailAddress=934800996@qq.com"
     * CN: 通用名称,域名  Common Name
     * O:  组织          Organization
     * OU: 部门          Organizational Unit
     * ST:  省份          State
     * L:  城市          Locality
     * C:  国家          Country
     */
    
    
    NSString *info =@"/CN=www.aaaa.com/O=aaaa.com/OU=IT/ST=Beijing City/L=beijing/C=CN/emailAddress=934800996@qq.com";
    char chDN[255] ;
    memcpy(chDN, [userInfo cStringUsingEncoding:NSASCIIStringEncoding], 2*[userInfo length]);
    
    char chCSR[2048] = {0};
    char privateKey[2048] = {0};
    
    
    long int rv = GenCSR(chDN, strlen(chDN), chCSR, sizeof(chCSR),privateKey);
    
    
   NSString* pkcs10=[NSString stringWithFormat:@"%s",chCSR];
   NSString* priKey=[NSString stringWithFormat:@"%s",privateKey];
    
    NSArray *resultArray = [NSArray arrayWithObjects:pkcs10,priKey, nil];
// 返回的数组的第一个为PKCS10 CSR证书请求,第二个值为(未加密的)私钥

 

 

最后给出请求证书的结果参考和私钥的结果参考:

 

 

-----BEGIN CERTIFICATE REQUEST-----

MIIDXzCCAkcCAQMwgY4xFTATBgNVBAMMDHd3dy5jaWNjLmNvbTERMA8GA1UECgwI

Y2ljYy5jb20xCzAJBgNVBAsMAklUMRUwEwYDVQQIDAxCZWlqaW5nIENpdHkxEDAO

BgNVBAcMB2JlaWppbmcxCzAJBgNVBAYMAkNOMR8wHQYJKoZIhvcNAQkBDBA5MzQ4

MDA5OTZAcXEuY29tMIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEAvlzj

emgqbuIIceej1iVqbrAcN+JM59kichhUv+dZNL2hWf3ya1xYZ+GCNZD52u8L6lmD

N+OKMGYfGgrKBcfvIIOGo99RnB0ipsq2lDd/pzfc26Ao1G2v+i1MNB4g1LeLAUnr

sC+alSAZoh0fuj2TmSRp0rbB/IRezqGt/S2zmYhebAE6+orn74bYtQ/H3Bu+ldOt

8KLn/FbmaRe8T5OJc0ayGE8dqEjQj0S9VHyCJVFumTwG18ZNdJyjRwn1K3lzX8gj

hpgtSaKHQwv4VpYNKDKrEBjsBYKPNVl0teSygFIXE2fmLoar43BfVM0LoVCKqXmt

H/OAENpCMmLSOgxYsQIBA6CBjDAZBgNVHRExEgwQRE5TOnd3dy5jaWNjLmNvbTAf

BgNVHSUxGAwWc2VydmVyQXV0aCwgY2xpZW50QXV0aDAjBglghkgBhvhCAQ0xFgwU

Q3JlYXRlIGJ5IGxpdWNoZW5nbGkwKQYDVR0PMSIMIGRpZ2l0YWxTaWduYXR1cmUs

IG5vblJlcHVkaWF0aW9uMA0GCSqGSIb3DQEBBQUAA4IBAQADg6ZJbaKhjirLbRj8

vanggxrmb4vyMx4qrNqEuem5dvbHJXu8qfzd3NkW8oYEOlyz45ndjP1ODQ6O1Bfa

dHJSgfqcPbqU3ZFHttXkgVKrbkJbzGWH2olWkeLBzbYqo0rjXtWmFdacw+O1+EJ/

VHzh2KyJLfo/w3wBmD5mFQpHwj5gMaEmZULrfgI6adR4yBacEafZQq1dCR67xwWB

AEh8xrOSsy85Bjm382+GbDdJIzHmZZcbo9kgjSjh3OASLRL8dsSupVedlJ/jQZpB

NII/8Llf9tVwcGnThAJ1P8zIp7Q8j9ErT+U15w0zTNoQ3yUny02RNR0/9xzZqrsg

1K6F

-----END CERTIFICATE REQUEST-----

 

 

 

 

 

-----BEGIN RSA PRIVATE KEY-----

MIIEogIBAAKCAQEAvlzjemgqbuIIceej1iVqbrAcN+JM59kichhUv+dZNL2hWf3y

a1xYZ+GCNZD52u8L6lmDN+OKMGYfGgrKBcfvIIOGo99RnB0ipsq2lDd/pzfc26Ao

1G2v+i1MNB4g1LeLAUnrsC+alSAZoh0fuj2TmSRp0rbB/IRezqGt/S2zmYhebAE6

+orn74bYtQ/H3Bu+ldOt8KLn/FbmaRe8T5OJc0ayGE8dqEjQj0S9VHyCJVFumTwG

18ZNdJyjRwn1K3lzX8gjhpgtSaKHQwv4VpYNKDKrEBjsBYKPNVl0teSygFIXE2fm

Loar43BfVM0LoVCKqXmtH/OAENpCMmLSOgxYsQIBAwKCAQB+6Jem8BxJ7AWhRRfk

GPGfIBLP7DNFO2xMEDh/75DN08DmqUxHkuWallbOYKaR9LKcO6zP7QbK7r9msdwD

2p9rAlnClOESvhcZ3Hm4JP/Ez+iSasXi88qmyN14FBXjJQdWMUfKymcOFWZsE2p8

KQ0QwvE3JIFTAunfFnP+Hne7rxhaObitPnLyuZaqtT1lVVB/YAAGr1trXzaR+iWU

NJH1bQ0GXXyGCpy1q4wSeu2ZL1mMfaxYCXTpUYTHCSZkCsqv/wnZhuQY09bHIEkL

4XlxF4Te3h3ktEb3jqvS68seYLYG+H6Pfh85e3+2rSI5TprnmAIXylLUhCAEFuBi

F3pLAoGBAOYj1UqPUQtHUtAKSnVqwoi8nWVMNZu2dMkZ1zrj1MfG9i5U/tFZOY6v

pSDU50BroX16e3M9/JAqbY3nj9tQmyV1qFRfFUPTrm97NZaHFbP3l8t2PbsxR9Yl

YLKIS4cQM0kaUzdCypGjv27Lq5Dh1HoWr+Ghe6rc4yA4jZ/2YVwVAoGBANPA1Vtn

XDA8HaaqtXZZWT0aJkiaZkMk00t0SUkNb+a8LPA5xBGF/1bPHkpj3N1T6OrMBBEV

u43sNM407GBEzh3iIUBhOJNQtlWdJfQ9riMG95hLjgP9LE6cosBxN3rCjbzun/EU

futpd7D3Hcdphig3ZchayFj1MPHzsuGwh8UtAoGBAJltONxfi1zaNzVcMaOcgbB9

vkOII70kTdtmj3yX4y/Z+XQ4qeDmJl8fw2s4mirya6j8UkzT/bVxnl6aX+eLEhj5

GuLqDi03yZ+neQ8EuSKlD9z5fnzLhTluQHcFh69gIjC8N3oshwvCf58ycmCWjaa5

ypZrp8c97MAls7/5lj1jAoGBAI0rOOeaPXV9aRnHI6Q7kNNmxDBm7tdt4jJNhjCz

n+8oHfV72AuuqjnfaYbtPejim0cyrWC5J7Pyzd7N8urYiWlBa4BA0GI1zuO+GU1+

dBdZ+mWHtAKoyDRobIBLelHXCSifFUti/0ebpSCkvoTxBBrPmTA8hZCjdfaid0Eg

WoNzAoGAdVakWYBw5J1qNRjqHRd0/RWRQWgVwr2De8hSCDMRv53wDI91YJruraQz

jggoHTAL0Db7G409aSbjvH/udQtNC6ySP/QpweMPWXsdYBDesoBfeUVcoYGjOgoH

t4Ecd9RhWesnDAeLUxYXyB/fKlHQhLiTYlO7GRJ+lYmYB/xJYN8=

-----END RSA PRIVATE KEY-----

 

 

 

 

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
您好!关于Java生成P10证书请求,可以使用Java中的KeyStore和CertificationRequestBuilder类库来实现。下面是一个简单的示例代码: ```java import java.io.IOException; import java.security.*; import java.security.cert.CertificateException; import java.util.Base64; import org.bouncycastle.asn1.pkcs.PKCS10CertificationRequest; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMWriter; public class PKCS10Generator { public static void main(String[] args) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException, IOException, CertificateException { Security.addProvider(new BouncyCastleProvider()); // 生成RSA密钥对 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(2048); KeyPair keyPair = keyPairGenerator.generateKeyPair(); // 创建证书生成请求 CertificationRequestBuilder builder = new JcaPKCS10CertificationRequestBuilder( new X500Name("CN=Test"), keyPair.getPublic() ); // 签名证书生成请求 JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder("SHA256withRSA"); ContentSigner signer = signerBuilder.build(keyPair.getPrivate()); PKCS10CertificationRequest csr = builder.build(signer); // 将证书请求转换为PEM格式并输出 PEMWriter pemWriter = new PEMWriter(new OutputStreamWriter(System.out)); pemWriter.writeObject(csr); pemWriter.close(); } } ``` 这段代码首先使用Bouncy Castle作为Java的安全提供程序,生成一个2048位的RSA密钥对。然后使用该密钥对创建一个PKCS10CertificationRequest生成请求,并使用SHA256withRSA算法对其签名。最后,将生成证书请求转换为PEM格式并输出。 请注意,为了使用Bouncy Castle提供程序,您需要将其添加到Java的安全提供程序列表中。您可以在代码的开头使用`Security.addProvider(new BouncyCastleProvider())`来完成此操作。 希望这可以帮助您生成P10证书请求。如果您有任何其他问题,请随时问我!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值