如何使用OpenSSL生成自签名SSL证书?

1932

我正在为一个嵌入式Linux设备添加HTTPS支持。我尝试使用以下步骤生成自签名证书:

openssl req -new > cert.csr
openssl rsa -in privkey.pem -out key.pem
openssl x509 -in cert.csr -out cert.pem -req -signkey key.pem -days 1001
cat key.pem>>cert.pem

这个方案是可行的,但在使用 Google Chrome 等浏览器时出现了一些错误:

这可能不是您正在寻找的网站!
网站的安全证书不受信任!

我是否漏掉了什么?这是构建自签名证书的正确方式吗?


63
自签名证书在互联网上被认为是不安全的。Firefox会将该网站视为具有无效证书,而Chrome则会像处理普通的HTTP连接一样处理。更多详情请访问:http://www.gerv.net/security/self-signed-certs/ - user1202136
62
您需要将您的CA证书导入浏览器,并告诉浏览器您信任该证书 - 或者 - 由已经被浏览器信任的一些大型机构签署该证书 - 或者 - 忽略警告并点击继续。我自己喜欢最后一个选项。 - trojanfoe
20
你不应该使用“存储” OpenSSL 设置。这是因为你不能在主体备用名称(SAN)中放置DNS名称。你需要提供一个带有alternate_names部分的配置文件,并使用-config选项传递它。同时,在通用名称(CN)中放置DNS名称被IETF和CA/Browser论坛弃用(但不被禁止)。在CN中的任何DNS名称也必须存在于SAN中。无法避免使用SAN。请参见下面的答案。 - jww
6
除了@jww的评论之外,根据2017年5月的规定,Chrome不再接受没有(空)SAN的证书:“此站点的证书不包含包含域名或IP地址的主题备用名称扩展。” - GerardJP
11
现在,只要您的Web服务器在互联网上通过FQDN(全限定域名)的80端口可以访问,就可以使用Let's Encrypt并获得免费的完整CA证书(有效期90天,可自动更新),这些证书不会导致任何浏览器警告或消息。请访问www.letsencrypt.com。 - DisappointedByUnaccountableMod
6
Let's Encrypt网站不是.com,而是.org - Yu Jiaao
23个回答

10

生成10年无需密码和证书的密钥,简单方法如下:

openssl req  -x509 -nodes -new  -keyout server.key -out server.crt -days 3650 -subj "/C=/ST=/L=/O=/OU=web/CN=www.server.com"

对于标志-subj | -subject,允许使用空值-subj "/C=/ST=/L=/O=/OU=web/CN=www.server.com",但您可以按照您的喜好设置更多细节:

  • C - 国家/地区名称(2字母代码)
  • ST - 州/省/地区
  • L - 地理位置名(例如城市)
  • O - 组织名称
  • OU - 组织单位名称
  • CN - 通用名称 - 必填!

9

openssl 允许通过单个命令生成自签名证书(-newkey 指令用于生成私钥,-x509 指令用于发放自签名证书而不是签名请求):

openssl req -x509 -newkey rsa:4096 \
-keyout my.key -passout pass:123456 -out my.crt \
-days 365 \
-subj /CN=localhost/O=home/C=US/emailAddress=me@mail.internal \
-addext "subjectAltName = DNS:localhost,DNS:web.internal,email:me@mail.internal" \
-addext keyUsage=digitalSignature -addext extendedKeyUsage=serverAuth

您可以分两步生成私钥并构建自签名证书:
openssl genrsa -out my.key -passout pass:123456 2048

openssl req -x509 \
-key my.key -passin pass:123456 -out my.csr \
-days 3650 \
-subj /CN=localhost/O=home/C=US/emailAddress=me@mail.internal \
-addext "subjectAltName = DNS:localhost,DNS:web.internal,email:me@mail.internal" \
-addext keyUsage=digitalSignature -addext extendedKeyUsage=serverAuth

查看生成的证书:

openssl x509 -text -noout -in my.crt

Java keytool 可以创建 PKCS#12 存储:

keytool -genkeypair -keystore my.p12 -alias master \
-storetype pkcs12 -keyalg RSA -keysize 2048 -validity 3650 \
-storepass 123456 \
-dname "CN=localhost,O=home,C=US" \
-ext 'san=dns:localhost,dns:web.internal,email:me@mail.internal'

导出自签名证书的方法如下:
keytool -exportcert -keystore my.p12 -file my.crt \
-alias master -rfc -storepass 123456

查看生成的证书:

keytool -printcert -file my.crt

GnuTLS中的certtool不支持通过命令行传递不同的属性。我不想去修改配置文件((


1
我再怎么强调也不为过!!!!!extendedKeyUsage = serverAuth, clientAuth 就是让我得到了“继续前往本地主机(不安全)”按钮的原因。 - JohnnyJS

8

2017年的一行代码版本:

CentOS:

openssl req -x509 -nodes -sha256 -newkey rsa:2048 \
-keyout localhost.key -out localhost.crt \
-days 3650 \
-subj "CN=localhost" \
-reqexts SAN -extensions SAN \
-config <(cat /etc/pki/tls/openssl.cnf <(printf "\n[SAN]\nsubjectAltName=IP:127.0.0.1,DNS:localhost"))

Ubuntu:

openssl req -x509 -nodes -sha256 -newkey rsa:2048 \
-keyout localhost.key -out localhost.crt \
-days 3650 \
-subj "/CN=localhost" \
-reqexts SAN -extensions SAN \
-config <(cat /etc/ssl/openssl.cnf <(printf "\n[SAN]\nsubjectAltName=IP:127.0.0.1,DNS:localhost"))

编辑:对于Ubuntu,将“subj”选项前加上斜杠。


7

Generate keys

I am using /etc/mysql for cert storage because /etc/apparmor.d/usr.sbin.mysqld contains /etc/mysql/*.pem r.

sudo su -
cd /etc/mysql
openssl genrsa -out ca-key.pem 2048;
openssl req -new -x509 -nodes -days 1000 -key ca-key.pem -out ca-cert.pem;
openssl req -newkey rsa:2048 -days 1000 -nodes -keyout server-key.pem -out server-req.pem;
openssl x509 -req -in server-req.pem -days 1000 -CA ca-cert.pem -CAkey ca-key.pem -set_serial 01 -out server-cert.pem;
openssl req -newkey rsa:2048 -days 1000 -nodes -keyout client-key.pem -out client-req.pem;
openssl x509 -req -in client-req.pem -days 1000 -CA ca-cert.pem -CAkey ca-key.pem -set_serial 01 -out client-cert.pem;

Add configuration

/etc/mysql/my.cnf

[client]
ssl-ca=/etc/mysql/ca-cert.pem
ssl-cert=/etc/mysql/client-cert.pem
ssl-key=/etc/mysql/client-key.pem

[mysqld]
ssl-ca=/etc/mysql/ca-cert.pem
ssl-cert=/etc/mysql/server-cert.pem
ssl-key=/etc/mysql/server-key.pem

在我的设置中,Ubuntu服务器记录在:/var/log/mysql/error.log

后续注意事项:

  • SSL error: Unable to get certificate from '...'

    MySQL might be denied read access to your certificate file if it is not in apparmors configuration. As mentioned in the previous steps^, save all our certificates as .pem files in the /etc/mysql/ directory which is approved by default by apparmor (or modify your apparmor/SELinux to allow access to wherever you stored them.)

  • SSL error: Unable to get private key

    Your MySQL server version may not support the default rsa:2048 format

    Convert generated rsa:2048 to plain rsa with:

    openssl rsa -in server-key.pem -out server-key.pem
    openssl rsa -in client-key.pem -out client-key.pem
    
  • Check if local server supports SSL:

    mysql -u root -p
    mysql> show variables like "%ssl%";
    +---------------+----------------------------+
    | Variable_name | Value                      |
    +---------------+----------------------------+
    | have_openssl  | YES                        |
    | have_ssl      | YES                        |
    | ssl_ca        | /etc/mysql/ca-cert.pem     |
    | ssl_capath    |                            |
    | ssl_cert      | /etc/mysql/server-cert.pem |
    | ssl_cipher    |                            |
    | ssl_key       | /etc/mysql/server-key.pem  |
    +---------------+----------------------------+
    
  • Verifying a connection to the database is SSL encrypted:

    Verifying connection

    When logged in to the MySQL instance, you can issue the query:

    show status like 'Ssl_cipher';
    

    If your connection is not encrypted, the result will be blank:

    mysql> show status like 'Ssl_cipher';
    +---------------+-------+
    | Variable_name | Value |
    +---------------+-------+
    | Ssl_cipher    |       |
    +---------------+-------+
    1 row in set (0.00 sec)
    

    Otherwise, it would show a non-zero length string for the cypher in use:

    mysql> show status like 'Ssl_cipher';
    +---------------+--------------------+
    | Variable_name | Value              |
    +---------------+--------------------+
    | Ssl_cipher    | DHE-RSA-AES256-SHA |
    +---------------+--------------------+
    1 row in set (0.00 sec)
    
  • Require ssl for specific user's connection ('require ssl'):

    • SSL

    Tells the server to permit only SSL-encrypted connections for the account.

    GRANT ALL PRIVILEGES ON test.* TO 'root'@'localhost'
      REQUIRE SSL;
    

    To connect, the client must specify the --ssl-ca option to authenticate the server certificate, and may additionally specify the --ssl-key and --ssl-cert options. If neither --ssl-ca option nor --ssl-capath option is specified, the client does not authenticate the server certificate.


备用链接:详细教程中的Secure PHP Connections to MySQL with SSL


1
-1;这与所问的问题大体上无关,而且没有清楚地表明引用的来源。 - Mark Amery
这显示了由CA提供的配置,由CA签名的服务器/客户端证书,并将它们配置为在带有apparmor的主机上由mysqld读取。这展示了一种相当无用的情况,即在同一台机器上托管ca、服务器和客户端,并危险地将该ca的权限暴露给mysqld进程。除了在测试环境中测试ssl配置之外,这种设置并没有真正意义。对于操作内部CA,我建议使用gnuttls工具链而不是openssl https://help.ubuntu.com/community/GnuTLS,并在处理mysqld+apparmor案例之前充分了解tls。 - ThorSummoner

7

一行命令胜过千言万语。我喜欢保持简单。为什么不使用一个包含所有必要参数的命令呢?这就是我喜欢的方式——它创建了一个x509证书及其PEM密钥:

openssl req -x509 \
 -nodes -days 365 -newkey rsa:4096 \
 -keyout self.key.pem \
 -out self-x509.crt \
 -subj "/C=US/ST=WA/L=Seattle/CN=example.com/emailAddress=someEmail@gmail.com"

这个单一的命令包含了你通常提供证书细节的所有答案。这样,你可以设置参数并运行命令,获取输出,然后去喝咖啡。

>> 更多信息 <<


1
除了SANs之外的所有参数... @vog的答案也涵盖了这一点(并且早于此)(尽管这个更完整的“主题”字段已经填写好了...)(我也不是很喜欢一年的到期时间) - Gert van den Berg
vog的回答。链接是因为用户名既不唯一也不不变。"vog"随时可能更改为"scoogie"。 - jpaugh

6
您的操作步骤基本正确。以下是该命令的语法。
openssl req -new -key {private key file} -out {output file}

然而,警告信息显示,浏览器无法通过验证证书与已知的证书颁发机构(CA)来验证身份。由于这是自签名证书,因此没有CA,并且您可以安全地忽略警告并继续操作。如果您想获取一个在公共互联网上任何人都可以识别的真正证书,则以下是相应的过程:
  1. 生成私钥
  2. 使用该私钥创建CSR文件
  3. 将CSR提交给CA(如Verisign或其他CA)
  4. 将从CA接收到的证书安装在Web服务器上
  5. 根据证书类型添加其他证书以进行身份验证链
我在使用OpenSSL创建安全证书:保护连接一文中有更多详细信息。

6

快速命令行:极简版

"我想要一个自签名的pfx格式证书,用最简单的方式为www.example.com生成":

openssl req -x509 -sha256 -days 365 -nodes -out cert.crt -keyout cert.key -subj "/CN=www.example.com"
openssl pkcs12 -export -out cert.pfx -inkey cert.key -in cert.crt

-sub 应该改为 -subj - milahu

5
正如详细讨论过的,自签名证书在互联网上不被信任。你可以将自签名证书添加到许多但并非所有浏览器中。或者,你可以成为自己的证书颁发机构
不想从证书颁发机构获取签名证书的主要原因是成本-Symantec 每年收费介于995-1999美元之间-仅针对内部网络的证书,Symantec每年收费399美元。如果你正在处理信用卡付款或为高利润公司的盈利中心工作,则很容易证明这种成本是可以接受的。但对于在互联网上创建个人项目或运行在最低预算上的非营利组织,或者在组织的成本中心工作的人来说,这已经超出了承受范围-成本中心总是尝试使用更少的资源完成更多的事情。
替代方案是使用Certbot(关于Certbot)。Certbot是一个易于使用的自动客户端,可为您的 Web 服务器获取和部署 SSL/TLS 证书。
如果你设置了Certbot,则可以启用它为你创建和维护由Let's Encrypt证书颁发机构颁发的证书。
我为我们的组织在周末完成了这项工作。我在我的服务器(Ubuntu16.04)上安装了certbot所需的包,然后运行了设置和启用certbot所需的命令。一般来说,你需要使用DNS插件来使用certbot - 我们目前正在使用DigitalOcean ,但可能很快就会迁移到另一个服务。

请注意,有些指令并不完全正确,需要经过一番试探和谷歌搜索才能搞清楚。第一次花费了相当多的时间,但现在我认为可以在几分钟内完成。

对于DigitalOcean,在输入DigitalOcean凭证INI文件路径时,我遇到了一些困难。脚本所指的是应用程序和API页面以及该页面上的Tokens / Key选项卡。您需要拥有或生成一个DigitalOcean API的个人访问令牌(读写),这是一个65个字符的十六进制字符串,然后将此字符串放入在运行certbot的web服务器上的文件中。该文件的第一行可以是注释(注释以#开头)。第二行是:

dns_digitalocean_token = 0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff

一旦我弄清如何为DigitalOcean的API设置读写令牌,使用certbot设置通配符证书就相当容易了。请注意,一个人不必设置通配符证书,可以指定要应用证书的每个域和子域。是通配符证书需要包含来自DigitalOcean的个人访问令牌的凭据INI文件。

请注意,公钥证书(也称为身份证书或SSL证书)会过期并需要更新。因此,您需要定期(周期性)更新证书。 certbot文档涵盖了续订证书的内容。

我的计划是编写一个脚本,使用openssl命令获取证书的到期日期,并在到期前30天或更短时间触发更新。然后将此脚本添加到cron并每天运行一次。

以下是读取证书到期日期的命令:

root@prod-host:~# /usr/bin/openssl x509 -enddate -noout -in path-to-certificate-pem-file
notAfter=May 25 19:24:12 2019 GMT

4

经过多次试错和尝试各种解决方案,我仍然遇到了一个问题:为localhost颁发自签名证书会导致错误。

ERR_CERT_INVALID

在展开详情后,谷歌浏览器会显示:

您无法访问 localhost,因为该网站发送了加密的凭据...

唯一的丑陋方法是在这个屏幕上直接输入(没有看到文本光标):

(在键盘上输入) thisisunsafe

这样才能继续进行。

直到我发现了extendedKeyUsage = serverAuth, clientAuth

简短总结

  1. openssl genrsa -out localhost.key 2048

  2. openssl req -key localhost.key -new -out localhost.csr

  3. (一切默认,只需在通用名称(CN)中填写localhost或其他FQDN即可。

  4. 将以下内容放入名为v3.ext的文件中(根据需要进行编辑):

subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always,issuer:always
basicConstraints       = CA:TRUE
keyUsage               = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign
extendedKeyUsage       = serverAuth, clientAuth
subjectAltName         = DNS:localhost, DNS:localhost.localdomain
issuerAltName          = issuer:copy
  1. openssl x509 -req -in localhost.csr -signkey localhost.key -out localhost.pem -days 3650 -sha256 -extfile v3.ext
然后完成!您可以访问网站,展开“高级选项”并单击“前往localhost(不安全)”。

也许有些聪明的家伙能够把所有这些变成一个漂亮的一行代码... - JohnnyJS
你上一个命令中的 v3.ext 文件是什么? - Daniel Klimuntowski
1
修改了答案,请查看第四点。 - JohnnyJS

2
这对我很有帮助。
openssl req -x509 -nodes -subj '/CN=localhost'  -newkey rsa:4096 -keyout ./sslcert/key.pem -out ./sslcert/cert.pem -days 365

server.js

var fs = require('fs');
var path = require('path');
var http = require('http');
var https = require('https');
var compression = require('compression');
var express = require('express');
var app = express();

app.use(compression());
app.use(express.static(__dirname + '/www'));    

app.get('/*', function(req,res) {
  res.sendFile(path.join(__dirname+'/www/index.html'));
});

// your express configuration here

var httpServer = http.createServer(app);
var credentials = {
    key: fs.readFileSync('./sslcert/key.pem', 'utf8'),
    cert: fs.readFileSync('./sslcert/cert.pem', 'utf8')
};
var httpsServer = https.createServer(credentials, app);

httpServer.listen(8080);
httpsServer.listen(8443);

console.log(`RUNNING ON  http://127.0.0.1:8080`);
console.log(`RUNNING ON  http://127.0.0.1:8443`);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接