Mbedtls和Opesnssl 解码x509Certificate
最近项目需要添加解码x509Certificate功能,可以使用openssl或者mbedtls库。对这两个库的使用总结一下。
一 Openssl解码x509 Certificate
1. 初始化
将一段buffer转化成openssl格式
const unsigned char* certificateValue = (unsigned char*)certificate->Value().data(); //这里的certificate是接收到的一段buffer
X509* m_certificate = d2i_X509(nullptr, &certificateValue, certificate->Value().size());
2. 获得版本号
int32_t certVersion = X509_get_version(m_certificate);
3. 获得序列号
const ASN1_INTEGER* ans1SerialNum = X509_get_serialNumber(m_certificate);
BIGNUM* bigSerialNUm = ASN1_INTEGER_to_BN(ans1SerialNum, nullptr);
char* serialNum = BN_bn2hex(bigSerialNUm);
serialNumber = std::string(serialNum, strlen(serialNum));
BN_free(bigSerialNUm);
OPENSSL_free(serialNum);
4. 获得公钥类型
const EVP_PKEY* pubKey = X509_get_pubkey(m_certificate);
switch (pubKey->type) {
case EVP_PKEY_RSA:
type = X509CertPubKeyType::PUB_KEY_TYPE_RSA;
break;
case EVP_PKEY_EC:
type = X509CertKeyAlgType::PUB_KEY_TYPE_ECKEY;
break;
case EVP_PKEY_DSA:
type = X509CertKeyAlgType::PUB_KEY_TYPE_ECDSA;
break;
case EVP_PKEY_DH:
type = X509CertKeyAlgType::PUB_KEY_TYPE_ECKEY_DH;
break;
default:
type = X509CertKeyAlgType::PUB_KEY_TYPE_UNKNOWN;
break;
}
5. 获得公钥使用类型
X509_check_ca(m_certificate);
if ((m_certificate->ex_kusage & KU_DATA_ENCIPHERMENT) == KU_DATA_ENCIPHERMENT) {
type = X509CertKeyUseType::KEY_USE_TYPE_EXCH;
}
else if ((m_certificate->ex_kusage & KU_DIGITAL_SIGNATURE) == KU_DIGITAL_SIGNATURE) {
type = X509CertKeyUseType::KEY_USE_TYPE_SIGN;
}
else {
type = X509CertKeyUseType::KEY_USE_TYPE_UNKNOWN;
}
6. 获得签名算法类型
const ASN1_OBJECT* signAlg = m_certificate->sig_alg.algorithm;
const int32_t oidMaxLen = 128;
char oid[oidMaxLen] = { 0 };
OBJ_obj2txt(oid, oidMaxLen, signAlg, 1);
std::string strOid(oid, strlen(oid)); const std::string CERT_SIG_ALG_RSA_RSA = "1.2.840.113549.1.1.1";
const std::string CERT_SIG_ALG_MD2RSA = "1.2.840.113549.1.1.2";
const std::string CERT_SIG_ALG_MD4RSA = "1.2.840.113549.1.1.3";
const std::string CERT_SIG_ALG_MD5RSA = "1.2.840.113549.1.1.4";
const std::string CERT_SIG_ALG_SHA1RSA = "1.2.840.113549.1.1.5";
const std::string CERT_SIG_ALG_SM3SM2 = "1.2.156.10197.1.501"; if (strOid == CERT_SIG_ALG_RSA_RSA) {
type = X509CertSigAlgType::SIG_ALG_TYPE_RSA_RSA;
}
else if (strOid == CERT_SIG_ALG_MD2RSA) {
type = X509CertSigAlgType::SIG_ALG_TYPE_MD2RSA;
}
else if (strOid == CERT_SIG_ALG_MD4RSA) {
type = X509CertSigAlgType::SIG_ALG_TYPE_MD4RSA;
}
else if (strOid == CERT_SIG_ALG_MD5RSA) {
type = X509CertSigAlgType::SIG_ALG_TYPE_MD5RSA;
}
else if (strOid == CERT_SIG_ALG_SHA1RSA) {
type = X509CertSigAlgType::SIG_ALG_TYPE_SHA1RSA;
}
else if (strOid == CERT_SIG_ALG_SM3SM2) {
type = X509CertSigAlgType::SIG_ALG_TYPE_SM3SM2;
}
else {
type = X509CertSigAlgType::SIG_ALG_TYPE_UNKNOWN;
}
7. 获得发布者名字
X509_NAME* issuerName = X509_get_issuer_name(m_certificate);
name = ConvertName(issuerName); //自定义函数
8. 获得证书持有者
X509_NAME* subjectName = X509_get_subject_name(m_certificate);
name = ConvertName(subjectName);
9. 获得证书有效时间起点
const ASN1_TIME* start = X509_get_notBefore(m_certificate);
time = ConvertTime(start);//自定义函数
10. 获得证书结束时间
const ASN1_TIME* end = X509_get_notAfter(m_certificate);
time = ConvertTime(end);
11. 获得公钥使用
const ASN1_BIT_STRING* keyUsage = (ASN1_BIT_STRING*)X509_get_ext_d2i(m_certificate, NID_key_usage, nullptr, nullptr);
uint16_t val = keyUsage->data[0];
if (keyUsage->length > 1) {
val |= keyUsage->data[1] << 8;
}
if (val & MBEDTLS_X509_KU_DIGITAL_SIGNATURE) {
usage += "Digital Signature, ";
}
if (val & MBEDTLS_X509_KU_NON_REPUDIATION) {
usage += "Non-Repudiation, ";
}
if (val & MBEDTLS_X509_KU_KEY_ENCIPHERMENT) {
usage += "Key Encipherment, ";
}
if (val & MBEDTLS_X509_KU_DATA_ENCIPHERMENT) {
usage += "Data Encipherment, ";
}
if (val & MBEDTLS_X509_KU_KEY_AGREEMENT) {
usage += "Key Agreement, ";
}
if (val & MBEDTLS_X509_KU_KEY_CERT_SIGN) {
usage += "Certificate Signature, ";
}
if (val & MBEDTLS_X509_KU_CRL_SIGN) {
usage += "CRL Signature, ";
}
const int32_t valMaxLen = 32;
char value[valMaxLen] = { 0 };
sprintf_s(value, valMaxLen, "(%x)", val);
usage += std::string(value, strlen(value));
12. 获得强化公钥使用
EXTENDED_KEY_USAGE* enUsage = (EXTENDED_KEY_USAGE*)X509_get_ext_d2i(m_certificate, NID_ext_key_usage, nullptr, nullptr);
for (int i = 0; i < sk_ASN1_OBJECT_num(enUsage); i++) {
const int32_t objMaxLen = 128;
char objId[objMaxLen] = { 0 };
char objName[objMaxLen] = { 0 };
const ASN1_OBJECT* obj = sk_ASN1_OBJECT_value(enUsage, i);
OBJ_obj2txt(objId, sizeof(objId), obj, 1);
OBJ_obj2txt(objName, sizeof(objName), obj, 0);
if (!usage.empty()) {
usage += "; ";
}
usage += objName + std::string(" (") + objId + ")";
}
sk_ASN1_OBJECT_pop_free(enUsage, ASN1_OBJECT_free);
13. 获得基础限制
BASIC_CONSTRAINTS* bcons = (BASIC_CONSTRAINTS*)X509_get_ext_d2i(m_certificate, NID_basic_constraints, nullptr, nullptr);
if (bcons->ca == 0) {
constraints += "Subject Type=End Entity; Path Length Constraint=None";
}
else {
std::string pathLenConstraint = nullptr == bcons->pathlen ? "None" : std::string((char*)bcons->pathlen->data);
constraints += "Subject Type=CA; " + std::string("Path Length Constraint=") + pathLenConstraint;
}
BASIC_CONSTRAINTS_free(bcons);
14. 获得SAN
STACK_OF(GENERAL_NAME)* extensions = (STACK_OF(GENERAL_NAME)*)X509_get_ext_d2i(m_certificate, NID_subject_alt_name, nullptr, nullptr);
for (int i = 0; i < sk_GENERAL_NAME_num(extensions); i++) {
const GENERAL_NAME* nval = sk_GENERAL_NAME_value(extensions, i);
if (nval->type == GEN_DNS) {
const unsigned char* dnsName = ASN1_STRING_get0_data(nval->d.dNSName);
dnsNames.push_back("DNS Name=" + std::string((const char*)dnsName));
}
else if (nval->type == GEN_IPADD) {
const unsigned char* ipAddr = ASN1_STRING_get0_data(nval->d.iPAddress);
ipAddrs.push_back("IP Address=" + ConvertIpAddr(ipAddr));//ConvertIpAddr是自定义函数
}
else if (nval->type == GEN_URI) {
const unsigned char* uri = ASN1_STRING_get0_data(nval->d.uniformResourceIdentifier);
uris.push_back("URL=" + std::string((const char*)uri));
}
else if (nval->type == GEN_DIRNAME) {
X509_NAME* dirName = nval->d.directoryName;
dirNames.push_back("Directory Name=" + ConvertName(dirName));
}
else if (nval->type == GEN_EMAIL) {
const unsigned char* email = ASN1_STRING_get0_data(nval->d.rfc822Name);
emails.push_back("RFC822 Name=" + std::string((const char*)email));
}
}
sk_GENERAL_NAME_pop_free(extensions, GENERAL_NAME_free)
15. 自定义函数ConvertName
std::string ConvertName(X509_NAME * name)
{
if (nullptr == name) {
return "";
}
const int32_t partNameMaxLen = 256;
char partName[partNameMaxLen] = { 0 };
std::string strName;
int returnLen = X509_NAME_get_text_by_NID(name, NID_countryName, partName, partNameMaxLen);
if (returnLen > 0) {
strName += "C=" + std::string(partName, strlen(partName)) + ", ";
}
memset(partName, 0, partNameMaxLen);
returnLen = X509_NAME_get_text_by_NID(name, NID_organizationalUnitName, partName, partNameMaxLen);
if (returnLen > 0) {
strName += "OU=" + std::string(partName, strlen(partName)) + ", ";
}
memset(partName, 0, partNameMaxLen);
returnLen = X509_NAME_get_text_by_NID(name, NID_commonName, partName, partNameMaxLen);
if (returnLen > 0) {
strName += "CN=" + std::string(partName, strlen(partName));
} return strName;
}
16. 自定义函数ConvertTime
std::string ConvertTime(const ASN1_TIME * time)
{
if (nullptr == time) {
return "";
}
std::shared_ptr<tm> tmTime(new tm());
int res = ASN1_TIME_to_tm(time, tmTime.get());
if (res == 0) {
return "";
}
const int32_t bufMaxLen = 256;
char buf[bufMaxLen] = { 0 };
int32_t basicYear = 1900;
int32_t basicMon = 1;
int32_t basicDay = 0;
int32_t basicHour = 8;
int32_t basicMin = 0;
int32_t basicSec = 0;
#ifdef _WIN32
sprintf_s(buf, "%d-%d-%d %d:%d:%d", tmTime->tm_year + basicYear, tmTime->tm_mon + basicMon, tmTime->tm_mday + basicDay,
tmTime->tm_hour + basicHour, tmTime->tm_min + basicMin, tmTime->tm_sec + basicSec);
#else
sprintf(buf, "%d-%d-%d %d:%d:%d", tmTime->tm_year + basicYear, tmTime->tm_mon + basicMon, tmTime->tm_mday + basicDay,
tmTime->tm_hour + basicHour, tmTime->tm_min + basicMin, tmTime->tm_sec + basicSec);
#endif
return std::string(buf, strlen(buf));
}
17. 自定义函数ConvertIp
std::string ConvertIpAddr(const unsigned char* ipv4octet)
{
if (nullptr == ipv4octet) {
return "";
}
std::string ipAddr;
for (auto i = 0; i < 4; i++)
{
if (!ipAddr.empty())
{
ipAddr += '.';
} char bits[4] = { 0 };
#ifdef _WIN32
sprintf_s(bits, sizeof(bits), "%d", ipv4octet[i]);
#else
snprintf(bits, sizeof(bits), "%d", ipv4octet[i]);
#endif // _WIN32
ipAddr.append(bits);
}
return ipAddr;
}
二 Mbedtls解码x509 Certificate
mbedtls的相关资料很少,自己也是研究了很长时间。并且SAN只支持Hostname
1. 初始化
将一段buffer转化成mbedtls类型
mbedtls_x509_crt_init(m_certificate);
uint32_t status = mbedtls_x509_crt_parse(m_certificate, (const unsigned char*)certificate->Value().data(), certificate->Value().size());
2. 获得版本号
int32_t certVersion = m_certificate->version;
3. 获得序列号
mbedtls_mpi mpi;
mbedtls_mpi_init(&mpi);
uint32_t status = mbedtls_mpi_read_binary(&mpi, m_certificate->serial.p, m_certificate->serial.len);
const int32_t strMaxLen = 128;
char str[strMaxLen] = { 0 };
size_t returnLen;
uint32_t radix = 16;
status = mbedtls_mpi_write_string(&mpi, radix, str, strMaxLen, &returnLen);
serialNumber = std::string(str, strlen(str));
mbedtls_mpi_free(&mpi);
4. 获得公钥类型
mbedtls_pk_type_t pubKeyType = mbedtls_pk_get_type(&m_certificate->pk);
switch (pubKeyType) {
case mbedtls_pk_type_t::MBEDTLS_PK_RSA:
type = X509CertPubKeyType::PUB_KEY_TYPE_RSA;
break;
case mbedtls_pk_type_t::MBEDTLS_PK_ECKEY:
type = X509CertPubKeyType::PUB_KEY_TYPE_ECKEY;
break;
case mbedtls_pk_type_t::MBEDTLS_PK_ECKEY_DH:
type = X509CertPubKeyType::PUB_KEY_TYPE_ECKEY_DH;
break;
case mbedtls_pk_type_t::MBEDTLS_PK_ECDSA:
type = X509CertPubKeyType::PUB_KEY_TYPE_ECDSA;
break;
case mbedtls_pk_type_t::MBEDTLS_PK_RSA_ALT:
type = X509CertPubKeyType::PUB_KEY_TYPE_RSA_ALT;
break;
case mbedtls_pk_type_t::MBEDTLS_PK_RSASSA_PSS:
type = X509CertPubKeyType::PUB_KEY_TYPE_RSASSA_PSS;
break;
default:
type = X509CertPubKeyType::PUB_KEY_TYPE_UNKNOWN;
break;
}
5. 获得公钥使用类型
if ((m_certificate->key_usage & MBEDTLS_X509_KU_DATA_ENCIPHERMENT) == MBEDTLS_X509_KU_DATA_ENCIPHERMENT) {
type = X509CertKeyUseType::KEY_USE_TYPE_EXCH;
}
else if ((m_certificate->key_usage & MBEDTLS_X509_KU_DIGITAL_SIGNATURE) == MBEDTLS_X509_KU_DIGITAL_SIGNATURE) {
type = X509CertKeyUseType::KEY_USE_TYPE_SIGN;
}
else {
type = X509CertKeyUseType::KEY_USE_TYPE_UNKNOWN;
}
6. 获得签名算法类型
mbedtls_md_type_t mdType;
mbedtls_pk_type_t pkType;
uint32_t status = mbedtls_oid_get_sig_alg(&m_certificate->sig_oid, &mdType, &pkType);
if (mdType == MBEDTLS_MD_MD2 && pkType == MBEDTLS_PK_RSA) {
type = X509CertSigAlgType::SIG_ALG_TYPE_MD2RSA;
}
else if (mdType == MBEDTLS_MD_MD4 && pkType == MBEDTLS_PK_RSA) {
type = X509CertSigAlgType::SIG_ALG_TYPE_MD4RSA;
}
else if (mdType == MBEDTLS_MD_MD5 && pkType == MBEDTLS_PK_RSA) {
type = X509CertSigAlgType::SIG_ALG_TYPE_MD5RSA;
}
else if (mdType == MBEDTLS_MD_SHA1 && pkType == MBEDTLS_PK_RSA) {
type = X509CertSigAlgType::SIG_ALG_TYPE_SHA1RSA;
}
else if (mdType == MBEDTLS_MD_SHA224 && pkType == MBEDTLS_PK_RSA) {
type = X509CertSigAlgType::SIG_ALG_TYPE_SHA224RSA;
}
else if (mdType == MBEDTLS_MD_SHA256 && pkType == MBEDTLS_PK_RSA) {
type = X509CertSigAlgType::SIG_ALG_TYPE_SHA256RSA;
}
else if (mdType == MBEDTLS_MD_SHA384 && pkType == MBEDTLS_PK_RSA) {
type = X509CertSigAlgType::SIG_ALG_TYPE_SHA384RSA;
}
else if (mdType == MBEDTLS_MD_SHA512 && pkType == MBEDTLS_PK_RSA) {
type = X509CertSigAlgType::SIG_ALG_TYPE_SHA512RSA;
}
else {
type = X509CertSigAlgType::SIG_ALG_TYPE_UNKNOWN;
}
7. 获得发布者名字
const char* shortName = nullptr;
uint32_t status = OpcUa_Good;
do{
if (MBEDTLS_ASN1_UTF8_STRING != m_certificate->issuer.val.tag) {
continue;
}
status = mbedtls_oid_get_attr_short_name(&m_certificate->issuer.oid, &shortName);
name += shortName + std::string("=") + std::string((char*)m_certificate->issuer.val.p, m_certificate->issuer.val.len);
}while (nullptr != m_certificate->issuer.next);
8. 获得证书持有者
const char* shortName = nullptr;
uint32_t status = OpcUa_Good;
do {
if (MBEDTLS_ASN1_UTF8_STRING != m_certificate->subject.val.tag)
{
continue;
}
status = mbedtls_oid_get_attr_short_name(&m_certificate->subject.oid, &shortName);
name += shortName + std::string("=") + std::string((char*)m_certificate->subject.val.p, m_certificate->subject.val.len);
} while (nullptr != m_certificate->subject.next);
9. 获得证书起始时间
const int32_t bufMaxLen = 256;
char buf[bufMaxLen] = { 0 };
uint32_t basicHour = 8;
sprintf_s(buf, "%d-%d-%d %d:%d:%d", m_certificate->valid_from.year, m_certificate->valid_from.mon, m_certificate->valid_from.day,
m_certificate->valid_from.hour + basicHour, m_certificate->valid_from.min, m_certificate->valid_from.sec);
time = std::string(buf, strlen(buf));
10. 获得证书结束时间
const int32_t bufMaxLen = 256;
char buf[bufMaxLen] = { 0 };
uint32_t basicHour = 8;
sprintf_s(buf, "%d-%d-%d %d:%d:%d", m_certificate->valid_to.year, m_certificate->valid_to.mon, m_certificate->valid_to.day,
m_certificate->valid_to.hour + basicHour, m_certificate->valid_to.min, m_certificate->valid_to.sec);
time = std::string(buf, strlen(buf));
11. 获得证书使用
uint32_t val = m_certificate->key_usage;
if (val & MBEDTLS_X509_KU_DIGITAL_SIGNATURE) {
usage += "Digital Signature, ";
}
if (val & MBEDTLS_X509_KU_NON_REPUDIATION) {
usage += "Non-Repudiation, ";
}
if (val & MBEDTLS_X509_KU_KEY_ENCIPHERMENT) {
usage += "Key Encipherment, ";
}
if (val & MBEDTLS_X509_KU_DATA_ENCIPHERMENT) {
usage += "Data Encipherment, ";
}
if (val & MBEDTLS_X509_KU_KEY_AGREEMENT) {
usage += "Key Agreement, ";
}
if (val & MBEDTLS_X509_KU_KEY_CERT_SIGN) {
usage += "Certificate Signature, ";
}
if (val & MBEDTLS_X509_KU_CRL_SIGN) {
usage += "CRL Signature, ";
}
const int32_t valMaxLen = 32;
char value[valMaxLen] = { 0 };
sprintf_s(value, valMaxLen, "(%x)", val);
usage += std::string(value, strlen(value));
12. 获得强化公钥使用
mbedtls_x509_sequence* enKeyUsage = &m_certificate->ext_key_usage;
while( nullptr != enKeyUsage) {
const char* des = nullptr;
uint32_t status = mbedtls_oid_get_extended_key_usage(&enKeyUsage->buf, &des);
const int valMaxLen = 128;
char val[valMaxLen] = { 0 };
status = mbedtls_oid_get_numeric_string(val, valMaxLen, &enKeyUsage->buf);
if (!usage.empty()) {
usage += ";";
}
usage += des + std::string(" (") + std::string(val, strlen(val)) + ")";
enKeyUsage = enKeyUsage->next;
}
13. 获得基础限制
if (m_certificate->ca_istrue == 0) {
constraints = "Subject Type=End Entity; Path Length Constraint=None";
}
else {
std::string pathLenConstraint = 0 == m_certificate->max_pathlen ? "None" : std::to_string(m_certificate->max_pathlen);
constraints += "Subject Type=CA; " + std::string("Path Length Constraint=") + pathLenConstraint;
}
14. 获得SAN(仅支持Hostname)
mbedtls_asn1_sequence* san = &m_certificate->subject_alt_names;
while (nullptr != san) {
dnsNames.push_back(std::string((char*)san->buf.p, san->buf.len));//dsnNames类型是std::vector<std::string>
san = san->next;
}
纯原创,参考请标明出处,谢谢!!
Mbedtls和Opesnssl 解码x509Certificate的更多相关文章
- 基于MbedTLS的AES加密实现,含STM32H7和STM32F4的实现例程
说明: 1.mbedTLS的前身是PolarSSL,开源免费. 主要提供了的SSL/TLS支持(在传输层对网络进行加密),各种加密算法,各种哈希算法,随机数生成以及X.509(密码学里公钥证书的格式标 ...
- 痞子衡嵌入式:对比MbedTLS算法库纯软件实现与i.MXRT上DCP,CAAM硬件加速器实现性能差异
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是MbedTLS算法库纯软件实现与i.MXRT上DCP,CAAM硬件加速器实现性能差异. 近期有 i.MXRT 客户在集成 OTA SBL ...
- linux字符串url编码与解码
编码的两种方式 echo '手机' | tr -d '\n' | xxd -plain | sed 's/\(..\)/%\1/g' echo '手机' |tr -d '\n' |od -An -tx ...
- URI编码解码和base64
概述 对于uri的编解码,在js中有3对函数,分别是escape/unescape,encodeURI/decodeURI,encodeURIComponent/decodeURIComponent. ...
- FFmpeg学习2:解码数据结构及函数总结
在上一篇文章中,对FFmpeg的视频解码过程做了一个总结.由于才接触FFmpeg,还是挺陌生的,这里就解码过程再做一个总结. 本文的总结分为以下两个部分: 数据读取,主要关注在解码过程中所用到的FFm ...
- Unicode转义(\uXXXX)的编码和解码
在涉及Web前端开发时, 有时会遇到\uXXXX格式表示的字符, 其中XXXX是16进制数字的字符串表示形式, 在js中这个叫Unicode转义字符, 和\n \r同属于转义字符. 在其他语言中也有类 ...
- C# base 64图片编码解码
使用WinForm实现了图片base64编码解码的 效果图: 示例base 64编码字符串: /9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKD ...
- java编码原理,java编码和解码问题
java的编码方式原理 java的JVM的缺省编码方式由系统的“本地语言环境”设置确定,和操作系统的类型无关 . 在JAVA源文件-->JAVAC-->Class-->Java--& ...
- [LeetCode] Decode String 解码字符串
Given an encoded string, return it's decoded string. The encoding rule is: k[encoded_string], where ...
随机推荐
- [FJOI2020]染色图的联通性问题 题解
FJOI2020 D1T2 题目大意 给出一个由 $n$ 个点 $m$ 条边构成的染色无向图,求删去每一个点及与其相连的边后图中不连通的同色点对数量.$n,m\leq 10^5$. 思路分析 可以想到 ...
- Spark Java创建DataFrame
以前用Python和Scala操作Spark的时候比较多,毕竟Python和Scala代码写起来要简洁很多. 今天一起来看看Java版本怎么创建DataFrame,代码写起来其实差不多,毕竟公用同一套 ...
- for...in、for...of和forEach
let arr = [1,2,3,4,5,6]; arr.name="AAA"; for(var i in arr){ //遍历的实际是对象的属性名臣,每一个元素的索引被视为一个属 ...
- 替换unimrcp的VAD模块
摘要: unimrcp vad 模块voice activity dector一直认为比较粗暴,而且unimrcp的社区也很久没有更新了.使用原始unimrcp如果只是用来做Demo演示,通过手动调整 ...
- 特性预览:Apache 顶级项目 Apache Pulsar 2.6.1 版本
在正式分享 2.6.1 版本更新细节之前,冉小龙首先为我们分享了两个相关 PIP 的内容. 一个是 PIP-47 中关于「基于时间来进行版本更新」的计划.该 PIP 提出后,从 2.5.0 版本到目前 ...
- 设置android studio启动时不检查sdk Android studio启动时总是在找AndroidSDK的解决办法
安装完android studio后,首次启动会弹出检查sdk组件等设置,点击finish会去下载sdk等,如果没有设置代理的情况下,这个界面会卡很久.截图如下: blog0826-1.png 所以, ...
- android 数据绑定(6)自定义绑定方法、双向数据绑定
1.官方文档 https://developer.android.com/topic/libraries/data-binding/binding-adapters https://developer ...
- vue-devtools-4.1.4_0.crx及Vue.js not detected的问题
谷歌-更多工具-扩展程序 Vue.js not detected的问题
- docker 停止、启动、删除镜像指令
容器 docker ps // 查看所有正在运行容器 docker stop containerId // containerId 是容器的ID docker ps -a // 查看所有容器 dock ...
- Nginx之https配置
14.1. 对称加密 安全隐患:钥匙除我之外,还有多个人拥有.泄露风险较大,钥匙传递的过程风险较大 14.2. 非对称加密 优缺点:私钥很安全.但是非对称算法开销很大,大批量应用于业务,会导致性能成本 ...