2024-08-10

sslMode 是 MySQL 连接串中用来指定 SSL 加密方式的参数。MySQL JDBC 连接器支持以下 SSL 模式:

  • DISABLED:不使用 SSL 加密。
  • PREFERRED:优先使用 SSL 加密,但如果不可用,也可以使用非加密连接。
  • REQUIRED:强制使用 SSL 加密,如果没有可用的 SSL 连接,连接尝试将失败。
  • VERIFY_CA:类似于 REQUIRED,但额外要求必须验证服务器的 SSL 证书是由信任的 CA 签发的。
  • VERIFY_IDENTITY:类似于 VERIFY_CA,但额外要求服务器的 SSL 证书必须确切匹配服务器的身份标识。

useSSL 和 requireSSL 是 MySQL 驱动程序中的参数,用来控制是否在客户端和服务器之间建立 SSL 连接。useSSL 通常用于 Java 连接器,而 requireSSL 用于其他语言的连接器。

sslMode 和 useSSL 之间的关系:sslMode 是高级设置,它覆盖了 useSSL 的设置,因为它能提供更多的控制和灵活性。如果你同时设置了 sslMode 和 useSSL,sslMode 将优先。

sslMode 和 requireSSL 之间的关系:sslMode 同样覆盖了 requireSSL 的设置,因为它是更通用的设置。

示例代码:




// 使用sslMode=REQUIRED确保SSL加密
String url = "jdbc:mysql://hostname:port/dbname?sslMode=REQUIRED";

或者在连接字符串中使用 useSSL=true(对于兼容的 MySQL JDBC 驱动程序):




// 使用useSSL=true来确保SSL加密
String url = "jdbc:mysql://hostname:port/dbname?useSSL=true";

请注意,具体的连接串参数可能会随着 MySQL 版本和 JDBC 驱动程序版本的不同而有所变化。始终建议参考最新的官方文档以获取最准确的信息。

2024-08-10

报错解释:

这个错误通常发生在使用PHP的cURL或者类似网络请求库进行HTTPS请求时,cURL无法验证SSL证书链,因为本地环境缺少必要的证书文件或者cURL没有正确配置。

解决方法:

  1. 下载或者复制CA证书:

    你可以从http://curl.haxx.se/docs/caextract.html下载最新的CA证书文件,通常命名为\`cacert.pem\`。

  2. 配置PHP的cURL或相应的库:

    • 如果你使用的是cURL,可以通过cURL的选项设置CA证书的路径,例如:

      
      
      
      curl_setopt($ch, CURLOPT_CAINFO, '/path/to/cacert.pem');
    • 如果你使用的是PHP的stream包,可以通过在php.ini文件中设置openssl.cafile指向你的CA证书文件:

      
      
      
      openssl.cafile=/path/to/cacert.pem
  3. 确保你的系统环境中包含了CA证书,并且PHP配置正确地引用了它们。
  4. 如果你不想修改PHP配置或者不想管理CA证书更新,可以尝试设置环境变量SSL_CERT_FILE指向CA证书文件:

    
    
    
    export SSL_CERT_FILE=/path/to/cacert.pem
  5. 另一个快速的解决方案是使用curl命令行工具,通过CURLOPT_CAINFO选项指定CA证书,然后在PHP脚本中调用这个命令。

确保在修改配置或代码后重启相关服务,并测试以验证问题是否已解决。

2024-08-09

在Java中调用HTTPS接口并绕过SSL认证通常不推荐,因为它会带来安全风险。但如果你了解这些风险并且确实需要这么做,可以使用以下方法:




import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import java.net.URL;
 
public class HttpClientUtil {
 
    public static void main(String[] args) throws Exception {
        URL url = new URL("https://your-https-api.com");
 
        // 创建SSLContext
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, new TrustAllTrustManager(), new java.security.SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
 
        // 忽略HTTPS证书验证
        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
        connection.setHostnameVerifier(new TrustAllHostnameVerifier());
 
        // 执行请求
        int status = connection.getResponseCode();
        System.out.println("Response Code: " + status);
 
        // 关闭连接
        connection.disconnect();
    }
 
    // 信任所有主机名的HostnameVerifier
    private static class TrustAllHostnameVerifier implements HostnameVerifier {
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    }
 
    // 信任所有SSL证书的TrustManager
    private static class TrustAllTrustManager implements X509TrustManager {
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }
 
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }
 
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
    }
}

请注意,这段代码中TrustAllTrustManager和TrustAllHostnameVerifier实现了SSL证书和主机名的信任,这会使得连接容易受到中间人攻击,不建议在生产环境中使用。在实际应用中,应该使用有效的SSL证书,并正确配置SSL上下文以确保安全通信。

2024-08-09



import requests
 
# 发送AJAX请求的示例函数
def send_ajax_request(url, session, headers=None):
    try:
        response = session.get(url, headers=headers, timeout=5)
        if response.status_code == 200:
            return response.json()
        else:
            print(f"请求失败,状态码: {response.status_code}")
    except requests.exceptions.RequestException as e:
        print(f"请求出错: {e}")
    except requests.exceptions.Timeout as e:
        print(f"请求超时: {e}")
    except requests.exceptions.TooManyRedirects as e:
        print(f"重定向过多: {e}")
    except requests.exceptions.HTTPError as e:
        print(f"HTTP错误: {e}")
    except requests.exceptions.ConnectionError as e:
        print(f"连接错误: {e}")
    except requests.exceptions.SSLError as e:
        print(f"SSL错误: {e}")
 
# 使用示例
url = "https://api.example.com/data"
headers = {"User-Agent": "My User Agent 1.0"}
 
# 创建一个requests.Session实例
session = requests.Session()
 
# 发送AJAX请求
send_ajax_request(url, session, headers)

这个代码示例展示了如何使用requests库来发送AJAX请求,并处理可能出现的异常。它创建了一个requests.Session实例来管理cookie,并且在发送请求时指定了headers。对于出现的异常,它打印出错误信息,这有助于调试和了解发生了什么。

2024-08-09

在MySQL中,information_schema.processlist表提供了当前MySQL服务器上的线程列表信息。这个表中的每一行代表了一个当前运行的线程。下面是这个表的一些常见字段及其作用:

  1. ID:线程的唯一标识符,可以用来区分不同的线程。
  2. USER:执行此线程的MySQL用户。
  3. HOST:发送请求的客户端的主机名或IP地址。
  4. DB:当前连接的默认数据库名。
  5. COMMAND:线程执行的命令类型,例如Sleep、Query、Connect等。
  6. TIME:线程处于当前状态的时间,以秒为单位。
  7. STATE:线程的状态,这是一个更详细的描述,比如updating、deleting等。
  8. INFO:线程正在执行的具体SQL语句。

这些字段提供了足够的信息来了解当前数据库中的活动和执行的查询,有助于数据库管理员进行性能监控和问题排查。例如,可以通过查看COMMAND和TIME字段来识别出长时间占用资源的查询。

2024-08-09

错误解释:

这个cURL错误表示客户端在尝试通过HTTPS协议与服务器通信时,遇到了SSL证书验证问题。具体来说,cURL无法获取本地颁发者证书(也就是CA证书链),这通常是因为本地系统或cURL没有正确配置相关的证书。

解决方法:

  1. 更新本地证书库:确保你的操作系统或者cURL都有最新的CA证书。在Linux系统中,你可以通过更新ca-certificates包来实现。
  2. 指定CA证书:如果你确定问题是由于本地证书库不完整或损坏,你可以指定一个完整的CA证书库给cURL使用。使用cURL的CURLOPT_CAINFO选项来指定路径。
  3. 禁用SSL证书验证(不推荐):如果你不介意安全风险,可以通过设置cURL的CURLOPT_SSL_VERIFYPEER为false来禁用SSL证书验证。这样可以绕过证书验证错误,但会使你的通信容易受到中间人攻击。

示例代码:




$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://example.com");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// 禁用SSL证书验证,不推荐
// curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
// 指定CA证书路径,如果需要
// curl_setopt($ch, CURLOPT_CAINFO, '/path/to/ca-bundle.crt');
$response = curl_exec($ch);
if (curl_errno($ch)) {
    echo 'cURL error: ' . curl_error($ch);
}
curl_close($ch);

请根据具体情况选择适当的解决方法。如果是开发环境,可以暂时选择禁用SSL证书验证进行快速调试,但在生产环境中应始终使用启用状态,以保证通信安全。

2024-08-09

'# node js 18以后由于 OpenSSL 加密,需要调整一下环境变量

一、背景与问题

Node.js 18版本引入了对OpenSSL 3.0的全面支持,这标志着Node.js生态在加密领域的重大变革。OpenSSL 3.0的显著改进包括:

  1. 引入FIPS(Federal Information Processing Standard)合规性验证机制
  2. 支持更丰富的加密算法套件
  3. 改变默认的配置文件加载方式
  4. 强化证书验证的严格程度

这一变更导致在Node.js 18及后续版本中,需要通过环境变量显式配置OpenSSL相关参数。例如:

# Linux/macOS
export OPENSSL_CONF=/usr/local/etc/openssl/openssl.cnf

# Windows
set OPENSSL_CONF=C:\OpenSSL\openssl.cnf

若未正确配置,可能会遇到以下典型错误:

Error: error:0308010C:PEM routines:OPENSSL_internal:incorrect PEM header

或

Error: error:0308010C:PEM routines:OPENSSL_internal:incorrect PEM header

二、基本原理

1. OpenSSL在Node.js中的角色

Node.js通过node:crypto模块调用OpenSSL库,其核心工作流程如下:

  1. 加载OpenSSL配置文件(openssl.cnf)
  2. 解析证书和私钥文件
  3. 使用OpenSSL API进行加密/解密操作
  4. 验证证书链有效性

在Node.js 18中,OpenSSL 3.0的引入带来了以下关键变化:

  • 配置文件加载机制改变:默认不再从/etc/openssl/openssl.cnf加载
  • 强化证书验证:默认启用strict模式,要求证书必须包含subjectAltName字段
  • 弃用部分旧算法:如SSLv2、SSLv3等协议

2. 环境变量的作用

Node.js通过以下环境变量控制OpenSSL行为:

  • OPENSSL_CONF:指定配置文件路径
  • SSL_CERT_FILE:指定证书文件路径(默认./cert.pem)
  • SSL_KEY_FILE:指定私钥文件路径(默认./privkey.pem)
  • NODE_TLS_REJECT_UNAUTHORIZED:控制是否拒绝未授权证书(默认1)

三、环境准备

1. 安装Node.js 18

# 使用nvm安装
nvm install 18

# 或直接安装
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs

2. 安装OpenSSL 3.0

# Linux
sudo apt-get install -y openssl

# macOS
brew install openssl@3

3. 配置环境变量

# Linux/macOS
export OPENSSL_CONF=/usr/local/etc/openssl/openssl.cnf
export SSL_CERT_FILE=/path/to/cert.pem
export SSL_KEY_FILE=/path/to/privkey.pem

# Windows
set OPENSSL_CONF=C:\OpenSSL\openssl.cnf
set SSL_CERT_FILE=C:\cert.pem
set SSL_KEY_FILE=C:\privkey.pem

四、核心实现

1. 基础配置示例

// app.js
const fs = require('fs');
const https = require('https');

const options = {
  key: fs.readFileSync(process.env.SSL_KEY_FILE || 'privkey.pem'),
  cert: fs.readFileSync(process.env.SSL_CERT_FILE || 'cert.pem'),
  // 启用严格证书验证
  rejectUnauthorized: true
};

https.createServer(options, (req, res) => {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello from HTTPS server\n');
}).listen(443, () => {
  console.log('HTTPS server running on port 443');
});

关键代码解释:

  • 使用process.env获取环境变量
  • 指定证书和私钥文件路径
  • 启用rejectUnauthorized严格验证模式

2. 配置文件示例

# openssl.cnf
[openssl_conf]
oid_section = oid_section
providers = provider_sect

[oid_section]
oid = 1.2.3.4.5.6.7.8.9.0

[provider_sect]
default = default
default-legacy = default
default-legacy-async = default

3. FIPS模式配置

// fips-check.js
const { FIPS } = require('node:crypto');

if (!FIPS) {
  console.error('FIPS mode not enabled');
  process.exit(1);
}

console.log('FIPS mode is enabled');

五、完整案例

1. HTTPS服务器实现

// server.js
const fs = require('fs');
const https = require('https');
const express = require('express');

const app = express();

// 证书和私钥路径配置
const certPath = process.env.SSL_CERT_FILE || './cert.pem';
const keyPath = process.env.SSL_KEY_FILE || './privkey.pem';

// 加载证书和私钥
const cert = fs.readFileSync(certPath);
const key = fs.readFileSync(keyPath);

// 创建HTTPS服务器
const options = {
  key: key,
  cert: cert,
  // 启用严格证书验证
  rejectUnauthorized: true
};

const server = https.createServer(options, (req, res) => {
  // 处理请求
  res.writeHead(200, {'Content-Type': 'application/json'});
  res.end(JSON.stringify({ status: 'ok', timestamp: Date.now() }));
});

// 启动服务器
server.listen(443, () => {
  console.log('HTTPS server running on port 443');
});

2. 客户端验证示例

// client.js
const https = require('https');

const options = {
  hostname: 'localhost',
  port: 443,
  // 指定信任的CA证书
  ca: fs.readFileSync('./ca-cert.pem')
};

https.get(options, (res) => {
  console.log('Status code:', res.statusCode);
  res.on('data', (chunk) => {
    console.log('Response:', chunk.toString());
  });
}).on('error', (err) => {
  console.error('Error:', err.message);
});

六、源码解析

1. Node.js源码中的OpenSSL配置

在node:crypto模块中,OpenSSL配置的加载逻辑位于:

// src/crypto/crypto_module.cc
void InitializeCryptoModule() {
  // 加载OpenSSL配置文件
  const char* openssl_conf = getenv("OPENSSL_CONF");
  if (openssl_conf) {
    OPENSSL_config(openssl_conf);
  } else {
    // 默认配置
    OPENSSL_config(NULL);
  }
}

2. 证书验证流程

在node:https模块中,证书验证流程涉及:

// src/http_client.cc
void SSL_set_alpn_protos(SSL* s, const unsigned char* protos, size_t len) {
  // 设置ALPN协议
}

七、进阶使用

1. 生产环境配置建议

# 生产环境配置文件
export OPENSSL_CONF=/etc/ssl/openssl.cnf
export SSL_CERT_FILE=/etc/ssl/certs/localhost.crt
export SSL_KEY_FILE=/etc/ssl/private/localhost.key

2. Docker容器配置

# Dockerfile
FROM node:18

# 设置环境变量
ENV OPENSSL_CONF=/etc/ssl/openssl.cnf \
    SSL_CERT_FILE=/etc/ssl/certs/localhost.crt \
    SSL_KEY_FILE=/etc/ssl/private/localhost.key

# 复制证书文件
COPY certs/ /etc/ssl/certs/
COPY keys/ /etc/ssl/private/

# 启动应用
CMD ["node", "server.js"]

3. 使用FIPS模式

# 启动FIPS模式
OPENSSL_FIPS=1 node server.js

八、性能与工程实践

1. 性能优化建议

  1. 使用内存缓存证书文件
  2. 避免频繁读取磁盘文件
  3. 启用TLS会话缓存
  4. 使用异步IO处理证书验证
// 优化后的证书加载
const certCache = {};

function getCertificate() {
  if (certCache[process.env.SSL_CERT_FILE]) {
    return Promise.resolve(certCache[process.env.SSL_CERT_FILE]);
  }
  return new Promise((resolve, reject) => {
    fs.readFile(process.env.SSL_CERT_FILE, (err, data) => {
      if (err) reject(err);
      certCache[process.env.SSL_CERT_FILE] = data;
      resolve(data);
    });
  });
}

2. 安全注意事项

  1. 避免硬编码证书文件路径
  2. 定期更新证书文件
  3. 使用强加密算法套件
  4. 配置ciphers参数限制弱算法
// 加密算法配置
const ciphers = 'TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256';

九、常见问题与踩坑

1. 常见错误及解决办法

错误信息原因解决方案
incorrect PEM header证书文件格式错误检查PEM格式是否正确
unable to get local issuer certificate未配置CA证书设置SSL_CERT_FILE环境变量
SSL_CTX_new() failedOpenSSL配置错误检查OPENSSL_CONF路径
FIPS mode not enabled未启用FIPS模式设置OPENSSL_FIPS=1环境变量

2. 典型错误示例

// 错误示例:硬编码证书路径
const cert = fs.readFileSync('./cert.pem');

// 正确做法:使用环境变量
const cert = fs.readFileSync(process.env.SSL_CERT_FILE || './cert.pem');

十、最佳实践

  1. 生产环境建议:

    • 使用独立的证书管理服务
    • 配置OPENSSL_CONF指向集中管理的配置文件
    • 启用FIPS模式确保合规性
  2. 开发环境建议:

    • 使用临时证书文件
    • 设置NODE_TLS_REJECT_UNAUTHORIZED=0临时禁用验证
    • 使用--openssl-legacy-provider启用旧版本兼容性
  3. 部署建议:

    • 在Docker容器中配置环境变量
    • 使用Kubernetes ConfigMap管理证书文件
    • 配置ciphers参数限制弱算法

十一、总结

Node.js 18对OpenSSL的升级带来了更安全的加密体系,但也要求开发者必须显式配置相关环境变量。本文深入分析了OpenSSL配置的工作原理,通过多个代码示例展示了如何正确配置环境变量,并提供了完整的HTTPS服务器实现案例。

在实际开发中,应根据具体场景选择合适的配置方案:生产环境建议启用FIPS模式并集中管理证书;开发环境可临时禁用验证以提高开发效率。同时需要特别注意证书管理的安全性,避免因配置错误导致的中间人攻击风险。

随着OpenSSL 3.0的普及,建议持续关注Node.js的更新动态,及时调整配置策略,确保系统始终处于安全、稳定的运行状态。

2024-08-09

'# 已解决:opensslErrorStack: [ 'error:03000086:digital envelope routines::initialization error' ]

一、背景与问题

在使用Node.js构建HTTPS服务时,我们经常遇到opensslErrorStack: [ 'error:03000086:digital envelope routines::initialization error' ]的错误。这个错误通常发生在初始化加密操作时,例如加载RSA密钥、处理PEM文件或进行TLS握手时。根据OpenSSL官方文档,该错误代码对应initialization error,意味着在初始化加密上下文或加载密钥时出现了不可恢复的错误。

这种错误可能由以下原因引起:

  • PEM文件格式不正确(缺少-----BEGIN...-----头)
  • 密钥密码错误或未正确设置
  • 证书链不完整或验证失败
  • 系统时间与证书时间戳不一致
  • 加密算法配置错误

二、基本原理

OpenSSL的初始化流程涉及三个关键步骤:

  1. 上下文创建:通过SSL_CTX_new()创建SSL上下文对象
  2. 密钥加载:使用SSL_CTX_use_PrivateKey_file()加载私钥
  3. 证书链验证:通过SSL_CTX_use_certificate_chain_file()加载证书链

当初始化过程中的任一步骤失败时,就会抛出error:03000086错误。需要特别注意的是,OpenSSL的digital envelope routines模块负责处理密钥加密和解密操作,其初始化失败通常意味着密钥文件本身存在结构性问题。

三、环境准备

# 安装Node.js环境
npm install -g node

# 创建项目目录
mkdir openssl-error-demo
cd openssl-error-demo
npm init -y
npm install express https

四、核心实现

1. PEM文件验证工具

// pem-validator.js
const fs = require('fs');

function validatePEM(filePath) {
    const content = fs.readFileSync(filePath, 'utf-8');
    const lines = content.split('\n');
    
    // 检查PEM头尾格式
    if (!lines[0].startsWith('-----BEGIN') || 
        !lines[lines.length - 1].endsWith('-----END')) {
        throw new Error('Invalid PEM format');
    }
    
    // 检查是否包含换行符
    if (/\r\n/.test(content)) {
        throw new Error('Windows CRLF line endings not allowed');
    }
    
    return content;
}

try {
    const pemContent = validatePEM('server.pem');
    console.log('PEM file is valid');
} catch (err) {
    console.error('PEM validation failed:', err.message);
}

关键代码解释:

  • 检查PEM文件是否包含标准头尾标记
  • 禁止使用Windows换行符(CRLF)
  • 验证文件内容是否包含正确的加密内容

2. RSA密钥初始化

// rsa-init.js
const fs = require('fs');
const crypto = require('crypto');

function initRSAKey(keyPath, password) {
    const key = fs.readFileSync(keyPath, 'utf-8');
    
    // 创建RSA密钥对象
    const pem = crypto.createCredentials({
        key: key,
        passphrase: password
    });
    
    // 验证密钥格式
    if (!pem.privateKey) {
        throw new Error('Invalid RSA key format');
    }
    
    return pem;
}

try {
    const rsaKey = initRSAKey('server.key', 'securepassword123');
    console.log('RSA key initialized successfully');
} catch (err) {
    console.error('RSA initialization failed:', err.message);
}

关键代码解释:

  • 使用createCredentials创建密钥对象
  • 检查是否成功解析私钥
  • 验证密钥格式是否符合PEM标准

3. TLS握手调试工具

// tls-debug.js
const tls = require('tls');
const fs = require('fs');

function debugTLSHandshake(certPath, keyPath, caPath) {
    const options = {
        cert: fs.readFileSync(certPath, 'utf-8'),
        key: fs.readFileSync(keyPath, 'utf-8'),
        ca: fs.readFileSync(caPath, 'utf-8'),
        rejectUnauthorized: false
    };
    
    const server = tls.createServer(options, (socket) => {
        console.log('Client connected');
        socket.write('Hello from server\n');
        socket.on('data', (data) => {
            console.log('Received:', data.toString());
            socket.destroy();
        });
    }).listen(8443, () => {
        console.log('Server listening on port 8443');
    });
    
    // 创建测试客户端
    const client = tls.connect(8443, 'localhost', () => {
        client.write('Hello from client\n');
        client.on('data', (data) => {
            console.log('Received:', data.toString());
            client.destroy();
        });
    });
}

关键代码解释:

  • 配置完整的TLS参数
  • 通过rejectUnauthorized: false禁用证书验证
  • 模拟客户端-服务器双向通信

五、完整案例:HTTPS服务搭建

// server.js
const https = require('https');
const fs = require('fs');
const express = require('express');

const app = express();

// 配置HTTPS服务器
const options = {
    key: fs.readFileSync('server.key', 'utf-8'),
    cert: fs.readFileSync('server.crt', 'utf-8'),
    ca: fs.readFileSync('ca.crt', 'utf-8'),
    passphrase: 'securepassword123'
};

app.get('/', (req, res) => {
    res.send('Hello from HTTPS server');
});

https.createServer(options, app).listen(8443, () => {
    console.log('HTTPS server running on port 8443');
});

运行流程:

  1. 确保server.key、server.crt、ca.crt文件存在
  2. 使用securepassword123作为密码
  3. 通过https://localhost:8443访问服务
  4. 观察是否出现初始化错误

六、源码解析

在Node.js的node_modules/node-openssl源码中,我们可以看到关键初始化流程:

// node-openssl/openssl.cc
void SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx, const char *file, int password) {
    BIO *bio = BIO_new_file(file, "r");
    if (!bio) {
        OPENSSL_error("Failed to open private key file");
        return;
    }
    
    EVP_PKEY *pkey = PEM_read_bio_PrivateKey(bio, NULL, password, NULL);
    if (!pkey) {
        OPENSSL_error("Failed to parse private key");
        BIO_free(bio);
        return;
    }
    
    if (!SSL_CTX_set_private_key(ctx, pkey)) {
        OPENSSL_error("Failed to set private key");
    }
    
    EVP_PKEY_free(pkey);
    BIO_free(bio);
}

关键点分析:

  • 使用BIO_new_file创建文件读取上下文
  • 调用PEM_read_bio_PrivateKey解析密钥
  • 检查密码是否正确
  • 设置私钥到SSL上下文

七、进阶使用

1. 多算法支持

const options = {
    key: fs.readFileSync('server.key', 'utf-8'),
    cert: fs.readFileSync('server.crt', 'utf-8'),
    ca: fs.readFileSync('ca.crt', 'utf-8'),
    passphrase: 'securepassword123',
    ciphers: 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256'
};

2. 模块化配置

// config.js
module.exports = {
    key: 'server.key',
    cert: 'server.crt',
    ca: 'ca.crt',
    passphrase: 'securepassword123'
};

3. 高级证书验证

const options = {
    key: fs.readFileSync('server.key', 'utf-8'),
    cert: fs.readFileSync('server.crt', 'utf-8'),
    ca: fs.readFileSync('ca.crt', 'utf-8'),
    passphrase: 'securepassword123',
    checkPeerCertificate: (cert) => {
        if (cert.expiry < Date.now()) {
            throw new Error('Certificate has expired');
        }
    }
};

八、性能与工程实践

1. 性能优化

  • 使用SSL_CTX_set_options启用SSL_OP_NO_TLSv1_1禁用旧协议
  • 使用SSL_CTX_set_min_proto_version设置最低协议版本
  • 预加载证书链:SSL_CTX_use_certificate_chain_file

2. 异常处理

try {
    const server = https.createServer(options, app);
} catch (err) {
    console.error('Server initialization failed:', err.message);
    process.exit(1);
}

3. 安全风险

  • 密钥管理不当可能导致私钥泄露
  • 证书链不完整可能造成中间人攻击
  • 密码过短或未加密可能被暴力破解

九、常见问题与踩坑

1. PEM文件格式错误

错误示例:

const key = fs.readFileSync('server.key', 'utf-8');

问题: 文件末尾缺少-----END RSA PRIVATE KEY-----标记

解决方法:

openssl rsa -in server.key -out server.key.pem

2. 密码错误

错误示例:

const options = { passphrase: 'wrongpassword' };

问题: 密码错误导致密钥无法解密

解决方法:

  • 使用openssl验证密码
  • 通过openssl rsa -in server.key -passout stdin交互式输入密码

3. 证书链不完整

错误示例:

const options = { ca: 'ca.crt' };

问题: 未包含中间证书导致验证失败

解决方法:

openssl x509 -in server.crt -out server.crt.pem -outform PEM
openssl x509 -in ca.crt -out ca.crt.pem -outform PEM

十、最佳实践

  1. 密钥管理规范

    • 使用openssl命令生成密钥
    • 使用openssl pkcs8转换密钥格式
    • 避免明文存储密码
  2. 证书验证策略

    • 使用openssl verify验证证书链
    • 设置checkPeerCertificate回调
    • 定期更新证书
  3. 性能优化策略

    • 启用SSL_OP_NO_TLSv1_1
    • 使用SSL_CTX_set_min_proto_version
    • 预加载证书链
  4. 错误处理规范

    • 使用try/catch捕获初始化错误
    • 记录详细的错误日志
    • 设置合理的超时机制

十一、总结

error:03000086:digital envelope routines::initialization error是OpenSSL初始化过程中常见的严重错误,其根本原因往往与密钥文件的格式、内容或配置相关。通过深入理解OpenSSL的初始化流程,我们可以有效避免此类问题。

在实际开发中,我们应当:

  • 严格遵循PEM文件格式规范
  • 使用强密码并妥善管理密钥
  • 定期验证证书链完整性
  • 优化TLS配置以提升性能

同时,也要注意在以下场景中谨慎使用:

  • 高并发场景需要优化密钥加载方式
  • 跨平台部署需注意不同系统对PEM格式的支持
  • 安全敏感场景需加强密钥保护机制

通过规范的配置和严谨的验证流程,我们可以有效避免此类错误,确保系统安全稳定运行。

2024-08-08

'# 爬虫爬取网页时报错:requests.exceptions.SSLError: HTTPSConnectionPool(host=‘www.cnblogs.com‘, port=443): Max r

一、背景与问题

在Python爬虫开发中,requests.exceptions.SSLError 是最常见的HTTPS连接错误之一。当我们尝试通过HTTPS协议爬取某些网站时,可能会遇到如下错误:

requests.exceptions.SSLError: HTTPSConnectionPool(host='www.cnblogs.com', port=443): Max retries exceeded with url

这个错误的核心原因是:爬虫在建立HTTPS连接时,SSL/TLS协议握手失败。常见原因包括:

  1. 服务器证书验证失败(证书过期、自签名证书、证书链不完整)
  2. 客户端SSL库版本过低(缺少支持现代TLS协议的实现)
  3. 网络代理配置错误
  4. 服务器端配置异常(如SSL证书与域名不匹配)

本文将深入解析该错误的原理,并提供完整的解决方案。

二、基本原理

1. HTTPS协议原理

HTTPS通过SSL/TLS协议对HTTP进行加密,其核心流程如下:

  1. 客户端发起HTTPS请求
  2. 服务器返回证书(包含公钥)
  3. 客户端验证证书有效性(CA签名、域名匹配、有效期等)
  4. 双方协商加密算法和密钥
  5. 建立加密通道进行数据传输

2. SSL证书验证机制

Python的requests库默认会验证服务器证书的以下要素:

  • 证书是否由可信CA签发
  • 证书是否在有效期内
  • 证书中的域名是否与请求的域名匹配
  • 证书链是否完整

当验证失败时会抛出SSLError异常。

三、环境准备

pip install requests

需要验证的网站:https://www.cnblogs.com(注意:该网站可能已变更HTTPS配置)

四、核心实现

1. 基础请求示例

import requests

response = requests.get('https://www.cnblogs.com')
print(response.status_code)

预期结果:返回200 OK状态码

实际结果:抛出SSLError异常

2. 处理SSL证书验证错误

方案一:忽略证书验证(不推荐生产环境使用)

import requests

response = requests.get(
    'https://www.cnblogs.com',
    verify=False  # 忽略证书验证
)
print(response.status_code)

说明:通过verify=False参数禁用证书验证,但会带来安全风险

方案二:使用本地CA证书

import requests

# 假设我们有一个本地CA证书文件 ca.crt
response = requests.get(
    'https://www.cnblogs.com',
    verify='/path/to/ca.crt'  # 指定本地CA证书路径
)
print(response.status_code)

说明:需要确保证书文件与服务器证书链匹配

3. 处理证书过期问题

import requests
from urllib3.exceptions import InsecureRequestWarning

# 禁用SSL警告
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

response = requests.get(
    'https://www.cnblogs.com',
    verify=False
)
print(response.status_code)

说明:在测试环境中可临时禁用警告,生产环境应避免

五、完整案例

案例:爬取CSDN博客内容(模拟场景)

import requests
from urllib3.exceptions import InsecureRequestWarning

# 配置参数
base_url = 'https://blog.csdn.net'
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36'
}

# 禁用SSL警告
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

def fetch_page(url):
    try:
        response = requests.get(url, headers=headers, verify=False, timeout=10)
        response.raise_for_status()  # 抛出HTTP错误
        return response.text
    except requests.exceptions.RequestException as e:
        print(f"请求失败: {e}")
        return None

# 使用示例
content = fetch_page(f"{base_url}/xxx")
if content:
    print("成功获取页面内容")
else:
    print("获取内容失败")

关键代码解释:

  1. verify=False:临时禁用证书验证
  2. timeout=10:设置超时时间防止无限等待
  3. raise_for_status():检查HTTP响应状态码

六、源码解析

1. requests库的SSL验证流程

# requests/sessions.py 中的核心逻辑
def send(self, request, **kwargs):
    # ... 其他代码
    if self.verify is not True:
        # 当 verify=False 时跳过证书验证
        kwargs['verify'] = False
    # ... 其他代码

2. SSL证书验证逻辑

# urllib3/connection.py 中的验证逻辑
def connect(self):
    # ... 其他代码
    if self.assert_hostname:
        # 验证证书域名匹配
        if not cert_name_matches(cert, self.host):
            raise SSLCertVerificationError(...)
    # ... 其他代码

七、进阶使用

1. 使用代理服务器

proxies = {
    'https': 'https://proxy.example.com:8888'
}

response = requests.get(
    'https://www.cnblogs.com',
    proxies=proxies,
    verify=False
)

2. 使用自定义SSL上下文

import ssl

context = ssl.create_default_context()
context.check_hostname = False  # 关闭域名验证
context.verify_mode = ssl.CERT_NONE  # 关闭证书验证

response = requests.get(
    'https://www.cnblogs.com',
    verify=context
)

3. 使用连接池优化性能

from requests.adapters import HTTPAdapter
from urllib3.util import retry

session = requests.Session()
session.mount('https://', HTTPAdapter(max_retries=3))

八、性能与工程实践

1. 性能优化

方案说明适用场景
使用连接池复用TCP连接高频访问同一服务器
设置超时防止无限等待网络不稳定场景
并行请求多线程/异步处理大量请求场景
压缩数据减少传输体积带宽受限场景

2. 安全注意事项

  • 证书验证:务必启用证书验证(verify=True),防止中间人攻击
  • 证书更新:定期更新CA证书库(certifi包)
  • 加密算法:优先使用TLSv1.2及以上协议
  • 证书匹配:确保证书域名与请求域名完全匹配(包括子域名)

3. 异常处理

try:
    response = requests.get(url, timeout=5)
except requests.exceptions.SSLError as e:
    print(f"SSL错误: {e}")
except requests.exceptions.Timeout:
    print("请求超时")
except requests.exceptions.RequestException as e:
    print(f"其他错误: {e}")

九、常见问题与踩坑

1. 常见错误及解决方案

问题原因解决方案
证书过期服务器证书未及时更新联系服务器管理员
自签名证书证书未被CA签名使用verify=False临时处理
域名不匹配证书域名与请求域名不一致检查域名拼写
协议版本不兼容服务器使用旧版TLS升级客户端SSL库

2. 常见陷阱

  • 忽略证书验证的隐患:可能导致中间人攻击(MITM)
  • 证书链不完整:需要包含所有中间证书
  • SSLv3协议不安全:需强制使用TLSv1.2及以上版本

十、最佳实践

1. 推荐方案

  1. 优先启用证书验证(verify=True)
  2. 使用最新版requests库(确保支持现代TLS协议)
  3. 配置合理的超时时间(建议5-10秒)
  4. 使用代理时配置验证(防止代理服务器劫持流量)

2. 不推荐方案

  1. 在生产环境使用verify=False
  2. 使用过期的CA证书库
  3. 不处理SSL协议版本
  4. 忽略证书域名匹配检查

十一、总结

requests.exceptions.SSLError 错误本质上是HTTPS协议安全机制的正常反馈,而非程序错误。在爬虫开发中,我们需要理解SSL/TLS协议的工作原理,正确配置证书验证,同时权衡安全性和功能性需求。

建议在实际项目中:

  • 开发阶段:启用证书验证,确保安全性
  • 测试阶段:可临时禁用验证进行调试
  • 生产环境:严格配置证书验证,防止中间人攻击

通过合理配置SSL参数、处理证书验证、优化连接管理,可以有效解决SSL相关错误,同时保证爬虫系统的安全性和稳定性。

2024-08-08

'# 解决According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by

一、背景与问题

MySQL 5.5.45+、5.6.26+ 和 5.7.6+ 版本开始引入强制SSL连接机制,要求客户端必须通过SSL协议与数据库建立连接。这一变更源于对数据传输安全性的提升需求,特别是在处理敏感数据(如用户密码、支付信息)时。

在开发中常见错误场景包括:

  1. 连接时提示 SSL connection is required 但未配置SSL参数
  2. 证书文件路径错误导致连接失败
  3. 自签名证书未被信任库识别
  4. 不同版本MySQL对SSL配置参数的兼容性差异

二、基本原理

MySQL强制SSL连接的核心机制包括:

  1. SSL协议握手:客户端与服务器通过TLS/SSL协议进行密钥交换
  2. 证书验证:客户端验证服务器证书的有效性(CA签名、有效期等)
  3. 加密传输:所有数据通过加密通道传输,防止中间人攻击
  4. 客户端配置:需要显式配置SSL参数(如证书路径、CA证书等)

三、环境准备

1. 服务器端配置

MySQL服务器需要配置SSL证书,需创建以下文件:

# 生成私钥
openssl genrsa -out server.key 2048

# 生成证书请求
openssl req -new -key server.key -out server.csr

# 生成自签名证书
openssl x509 -req -in server.csr -signkey server.key -out server.crt -days 365

# 配置MySQL
[mysqld]
ssl-ca=/path/to/ca.pem
ssl-cert=/path/to/server.pem
ssl-key=/path/to/server.key

2. 客户端配置

需要准备以下文件:

  • 客户端证书(client.pem)
  • CA证书(ca.pem)

四、核心实现

1. Python实现(使用mysql-connector)

import mysql.connector
from mysql.connector import Error

def connect_with_ssl():
    try:
        connection = mysql.connector.connect(
            host='localhost',
            user='root',
            password='your_password',
            database='test_db',
            ssl_ca='/path/to/ca.pem',  # CA证书路径
            ssl_cert='/path/to/client.pem',  # 客户端证书
            ssl_key='/path/to/client.key'  # 客户端私钥
        )
        print("SSL连接成功")
        return connection
    except Error as e:
        print(f"连接失败: {e}")
        return None

关键代码解释:

  • ssl_ca:指定CA证书路径,用于验证服务器证书
  • ssl_cert和ssl_key:客户端证书和私钥,用于双向SSL认证
  • 如果未指定ssl_ca,MySQL会尝试使用默认的CA证书库(通常位于/usr/local/etc/openssl/cert.pem)

2. Node.js实现(使用mysql2)

const { createPool } = require('mysql2');

const pool = createPool({
  host: 'localhost',
  user: 'root',
  password: 'your_password',
  database: 'test_db',
  ssl: {
    ca: fs.readFileSync('/path/to/ca.pem'),  // CA证书
    cert: fs.readFileSync('/path/to/client.pem'),  // 客户端证书
    key: fs.readFileSync('/path/to/client.key')  // 客户端私钥
  }
});

pool.query('SELECT 1', (err, rows) => {
  if (err) throw err;
  console.log("SSL连接成功");
});

关键代码解释:

  • ssl配置对象需要包含完整的证书链
  • 使用fs.readFileSync确保证书文件可读
  • Node.js默认不包含CA证书库,必须显式指定

3. PHP实现(使用PDO)

<?php
$dsn = 'mysql:host=localhost;dbname=test_db;charset=utf8mb4';
$opt = [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::MYSQL_ATTR_SSL_CA => '/path/to/ca.pem',
    PDO::MYSQL_ATTR_SSL_CERT => '/path/to/client.pem',
    PDO::MYSQL_ATTR_SSL_KEY => '/path/to/client.key'
];

try {
    $pdo = new PDO($dsn, 'root', 'your_password', $opt);
    echo "SSL连接成功";
} catch (PDOException $e) {
    echo "连接失败: " . $e->getMessage();
}
?>

关键代码解释:

  • PDO的MYSQL_ATTR_SSL_*参数需要明确指定
  • 如果未指定SSL_CA,PHP会尝试使用系统证书库(/etc/ssl/certs/ca-certificates.crt)

五、完整案例

案例:基于Flask的Web应用连接MySQL

1. 项目结构

ssl_mysql_demo/
├── app/
│   ├── __init__.py
│   └── models.py
├── config.py
├── requirements.txt
└── ssl_certificates/
    ├── ca.pem
    ├── server.pem
    ├── server.key
    ├── client.pem
    └── client.key

2. 安装依赖

pip install flask mysql-connector-python

3. 配置文件(config.py)

MYSQL_CONFIG = {
    'host': 'localhost',
    'user': 'root',
    'password': 'your_password',
    'database': 'test_db',
    'ssl_ca': '/ssl_certificates/ca.pem',
    'ssl_cert': '/ssl_certificates/client.pem',
    'ssl_key': '/ssl_certificates/client.key'
}

4. 模型文件(models.py)

import mysql.connector
from config import MYSQL_CONFIG

def get_db():
    return mysql.connector.connect(**MYSQL_CONFIG)

5. 应用入口(app/__init__.py)

from flask import Flask
from models import get_db

app = Flask(__name__)

@app.route('/test')
def test_connection():
    try:
        conn = get_db()
        cursor = conn.cursor()
        cursor.execute("SELECT 1")
        result = cursor.fetchone()
        cursor.close()
        return f"连接成功: {result}"
    except Exception as e:
        return f"连接失败: {str(e)}"

六、源码解析

1. MySQL SSL握手流程(简化版)

// mysql-connector-c源码片段
void connect_ssl() {
    SSL_CTX *ctx = SSL_CTX_new(TLSv1_2_client_method());
    SSL *ssl = SSL_new(ctx);
    
    // 加载CA证书
    SSL_CTX_load_verify_locations(ctx, ca_path, NULL);
    
    // 配置客户端证书
    SSL_use_certificate_file(ssl, client_cert, SSL_FILETYPE_PEM);
    SSL_use_key_file(ssl, client_key, SSL_FILETYPE_PEM);
    
    // 建立SSL连接
    SSL_set_fd(ssl, socket_fd);
    if (SSL_connect(ssl) <= 0) {
        // 处理错误
    }
}

关键点:

  • 使用SSL_CTX_load_verify_locations指定CA证书
  • 双向认证需要同时配置客户端证书和私钥
  • 不同SSL版本(TLSv1.2、TLSv1.3)需对应配置

2. Python连接池优化(使用mysql-connector)

from mysql.connector import pooling

def create_pool():
    return pooling.MySQLConnectionPool(
        pool_name="mypool",
        pool_size=10,
        host='localhost',
        user='root',
        password='your_password',
        database='test_db',
        ssl_ca='/ssl_certificates/ca.pem',
        ssl_cert='/ssl_certificates/client.pem',
        ssl_key='/ssl_certificates/client.key'
    )

七、进阶使用

1. 自动证书管理

在容器化部署中,可以使用Vault或Kubernetes Secrets管理证书:

import os
from mysql.connector import connection

def get_ssl_path():
    cert_path = os.getenv("SSL_CERT_PATH", "/etc/ssl/certs/client.pem")
    key_path = os.getenv("SSL_KEY_PATH", "/etc/ssl/private/client.key")
    ca_path = os.getenv("SSL_CA_PATH", "/etc/ssl/certs/ca.pem")
    return cert_path, key_path, ca_path

2. 灰度发布策略

在新版本部署时,可使用SSL_VERIFY_PEER参数控制验证强度:

config = {
    'ssl_verify_peer': 1,  # 强验证(默认)
    'ssl_verify_hostname': 2  # 验证主机名
}

八、性能与工程实践

1. 性能优化方案

优化项方法效果
证书缓存使用SSL_CTX_set_options预加载证书减少握手时间
协议选择强制使用TLSv1.2避免旧协议漏洞
连接池使用连接池复用连接降低建立新连接的开销
压缩传输启用SSL_COMPRESS_METHOD减少数据传输量

2. 安全风险分析

风险点防范措施
自签名证书使用CA签名证书并定期更新
证书泄露限制证书访问权限(chmod 600)
硬编码凭证使用环境变量或配置文件管理
未验证主机名设置ssl_verify_hostname为严格模式

九、常见问题与踩坑

1. 常见错误及解决办法

错误场景错误信息解决方案
证书路径错误SSL error: certificate verify failed检查ssl_ca路径是否正确
双向认证失败SSL error: certificate not trusted确保客户端证书被CA签名
协议不兼容SSL error: protocol version mismatch检查ssl_ca和ssl_cert版本匹配
端口冲突Connection refused确认MySQL端口(默认3306)是否开放

2. 版本兼容性问题

MySQL版本SSL配置要求兼容性说明
5.5.45+必须SSL支持双向认证
5.6.26+必须SSL引入ssl-mode参数
5.7.6+必须SSL强化证书验证

十、最佳实践

1. 推荐方案

  1. 生产环境:启用双向SSL认证,定期更新证书
  2. 开发环境:禁用SSL验证(仅用于测试)
  3. 混合部署:使用ssl-mode=VERIFY_IDENTITY进行严格验证
  4. 容器部署:使用Secrets管理证书,避免硬编码

2. 不推荐场景

  1. 内部系统:如果数据不敏感且网络环境安全
  2. 临时测试:使用ssl-mode=DISABLED快速验证
  3. 旧系统迁移:需评估现有系统是否支持SSL配置

十一、总结

MySQL强制SSL连接机制是提升数据安全性的关键措施,但需要开发者正确配置证书和参数。通过本篇博客,我们深入解析了SSL连接的工作原理,提供了多种编程语言的实现示例,并分析了常见错误及解决方案。在实际开发中,应根据业务需求选择合适的SSL配置策略,平衡安全性和性能需求。对于涉及敏感数据的系统,建议始终启用SSL连接,并定期维护证书管理流程。