ASP.NET:使用文件中的公钥和私钥对URL进行签名 [英] ASP.NET: Sign URL with public and private keys from files
问题描述
我想更新Google AMP缓存,因此我需要按照这里.
I want to update Google AMP cache, so I need to sign an URL as described here.
我的主要问题:我在如何获取证书/密钥以及如何将它们包含在下面的代码中而苦苦挣扎. 我只是找不到适用于Windows和IIS的所有涵盖说明.
My main issue: I'm struggling massively with how I should get my certificates/keys and how to include them in my code below. I just can't find any all covering instructions for Windows and IIS.
我一直在阅读这些帖子:
I have been reading these posts:
- Using /update-cache requests to update AMP pages
- How can I sign a file using RSA and SHA256 with .NET?
我不想使用第二篇文章中所述的计算机证书存储.我宁愿使用磁盘上的文件作为公钥和私钥. 从生产服务器IIS,我将证书导出到.pfx文件,然后使用
I don't want to use my computer's certificate store as described in the second post. I'd rather use files on disk for both public and private keys.
From my production server IIS, I exported my certificate to a .pfx file, from which I then extracted the private key and certificate using the instructions on the bottom of this site.
The server.key contains -----BEGIN RSA PRIVATE KEY-----
, which If I use that to load into the privateCert
variable in the code below throws error Cannot find the requested object.
我从SSL提供商那里得到了什么:
www_example_com.crt
,www_example_com.p7b
,certificate code
(请参见下文).
What I have gotten from my SSL provider:
www_example_com.crt
, www_example_com.p7b
, the certificate code
(see below).
我已采取的步骤:
- 我通过打开
www_example.com.crt
并使用Copy to file
向导将其复制到base64编码的.cer文件中来创建test-public-key.cer
. - 我保存了从SSL提供程序收到的
certificate code
作为文件test-private-key.cer
.
- I created
test-public-key.cer
by openingwww_example.com.crt
and using theCopy to file
wizard to copy it to a base64 encoded .cer file. - I saved
certificate code
I received from my SSL provider as filetest-private-key.cer
.
运行以下代码时出现错误
When I run the following code I get error
对象引用未设置为对象的实例.
Object reference not set to an instance of an object.
在线key.FromXmlString(privateCert.PrivateKey.ToXmlString(True))
Dim publicCert As X509Certificate2 = New X509Certificate2("C:\temp\_certificates\test-public-key.cer")
Dim privateCert As X509Certificate2 = New X509Certificate2("C:\temp\_certificates\test-private-key.cer")
'Round-trip the key to XML and back, there might be a better way but this works
Dim key As RSACryptoServiceProvider = New RSACryptoServiceProvider
key.FromXmlString(privateCert.PrivateKey.ToXmlString(True))
'Create some data to sign
Dim data() As Byte = System.Text.Encoding.Unicode.GetBytes(signatureUrl)
'Sign the data
Dim sig() As Byte = key.SignData(data, CryptoConfig.MapNameToOID("SHA256"))
Dim AMPURLSignature As String = EncodeTo64(sig.ToString)
'Lastly, the verification can be done directly with the certificate's public key without need for the reconstruction as we did with the private key:
key = CType(publicCert.PublicKey.Key, RSACryptoServiceProvider)
If Not key.VerifyData(data, CryptoConfig.MapNameToOID("SHA256"), sig) Then
Throw New CryptographicException
End If
EncodeTo64函数
EncodeTo64 function
Public Shared Function EncodeTo64(ByVal toEncode As String) As String
Dim toEncodeAsBytes As Byte() = System.Text.ASCIIEncoding.ASCII.GetBytes(toEncode)
Dim returnValue As String = System.Convert.ToBase64String(toEncodeAsBytes)
Return returnValue
End Function
certificate code
----- BEGIN证书-----
MIIF(...)DXuJ
-----结束证书-----
-----BEGIN CERTIFICATE-----
MIIF (...) DXuJ
-----END CERTIFICATE-----
更新1
I was able to generate a mysite.pfx file by following the export steps on this page. In the wizard I made sure to select "Yes, export the private key" and I added a password. The rest of the steps I followed verbatim.
然后我还运行了以下命令:
openssl pkcs12 -in mysite.pfx -nocerts -out private-key-VPS.pem
penssl pkcs12 -in mysite.pfx -clcerts -nokeys -out certificate-VPS.pem
I then also ran these commands:
openssl pkcs12 -in mysite.pfx -nocerts -out private-key-VPS.pem
penssl pkcs12 -in mysite.pfx -clcerts -nokeys -out certificate-VPS.pem
我最终得到了private-key-VPS.pem
和一个certificate-VPS.pem
文件
I ended up with private-key-VPS.pem
and a certificate-VPS.pem
files
我知道获取mysite.pfx的步骤与@CodeFuller所描述的步骤略有不同,但是到目前为止一切顺利吗?
然后我添加了代码:
Dim certificate As X509Certificate2 = New X509Certificate2("C:\temp\_certificates\prodserverV2\mysite.pfx", "mypwd")
Dim rsa As RSA = certificate.GetRSAPrivateKey()
Dim data() As Byte = System.Text.Encoding.Unicode.GetBytes(signatureUrl)
Dim sig() As Byte = rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)
Dim AMPURLSignature As String = EncodeTo64(sig.ToString)
但是我得到了4个错误:
But there I get 4 errors:
GetRSAPrivateKey'不是'X509Certificate2'的成员.
'SignData'不是'RSA'的成员.
未声明"HashAlgorithmName".由于其保护级别,因此可能无法访问.
未声明"RSASignaturePadding".由于其保护级别,它可能无法访问.
GetRSAPrivateKey' is not a member of 'X509Certificate2'.
'SignData' is not a member of 'RSA'.
'HashAlgorithmName' is not declared. It may be inaccessible due to its protection level.
'RSASignaturePadding' is not declared. It may be inaccessible due to its protection level.
更新2
感谢@CodeFuller!我针对的是4.6.1框架,现在似乎又往前迈了一步.我最终得到一个像这样的URL:https://www-mysite-com.cdn.ampproject.org/update-cache/c/s/www.mysite.com/articles/270/newarticle1/amp?amp_action=flush&_ts=1522939248&_url_signature=U30zdGVtLkJ5uGVbRQ==
.现在如何检查它是否是有效的URL?
我正在检查此页面上的生成RSA密钥"部分,但是我很困惑,因为我实际上已经对这些步骤进行了编码?如何检查我现在结尾的URL是否有效?
Thanks @CodeFuller! I targeted framework 4.6.1 and now seem to be 1 step further. I end up with an URL like this: https://www-mysite-com.cdn.ampproject.org/update-cache/c/s/www.mysite.com/articles/270/newarticle1/amp?amp_action=flush&_ts=1522939248&_url_signature=U30zdGVtLkJ5uGVbRQ==
. How can I now check if it's a valid URL?
I'm checking section "Generate the RSA key" on this page, but I'm confused, since I actually already just coded these steps or not? How can I check whether the URL I now end up with is valid?
更新3
好的,我尝试了您的新代码.仍然获取URL signature verification error
.我同时尝试了文章的/amp
URL和URL中的/amp
部分.两者都会导致相同的URL签名验证错误.
Ok, I tried your new code. Still get the URL signature verification error
. I tried with both the /amp
URL of my article and without the /amp
part in my URL. Both result in the same URL signature verification error.
当我将最终URL打印到我的网站时(请参见下面的代码),我注意到URL如下:
I noticed when I print the final URL to my website (see code below), the URL reads:
https://www-toptrouwen-nl.cdn.ampproject.org/update-cache/c/s/www.toptrouwen.nl/artikelen/132/het-uitwisselen-van-de-trouwringen-hoe-voorkom-je-bloopers/amp?amp_action=flush&_ts=1523094395&_url_signature=U3lzdGVrLkn5dGVbXQ==
请注意,在参数应为amp_ts
和amp_url_signature
的位置,它们现在分别为_ts
和_url_signature
.
Notice that where the parameters should be amp_ts
and amp_url_signature
, they now are _ts
and _url_signature
respectively.
在我致电Google之前,我曾尝试通过手动将参数_ts
和_url_signature
重命名为amp_ts
和amp_url_signature
来编辑URL.但是我想这会导致签名和实际URL之间的差异.难道是我的代码以某种方式破坏了&
字符,因此当我以后手动重命名这些字符时,总是会导致签名验证吗?您看到我的代码中可以解决的问题吗?
I tried editing the URL before I do the call to Google by manually renaming parameters _ts
and _url_signature
to amp_ts
and amp_url_signature
. But I guess that would result in a difference between the signature and the actual URL. Could it be that somehow my code botches the &
character and therefore when I rename these manually later it always result in a signature verification? Do you see what I could fix in my code?
顺便说一句:我在签名URL之前尝试用代码隐藏的&
替换&
,但是随后出现Google 404错误:
BTW: I tried replacing &
with %26
in code-behind before signing the URL but then I get a Google 404 error:
请求的URL/update-cache/c/s/www.toptrouwen.nl/artikelen/132/het-uitwisselen-van-de-trouwringen-hoe-voorkom-je-bloopers/amp?amp_action=flush%26amp_ts = 1523094395%26amp_url_signature =在此服务器上找不到U3lzdGVrLkJ1dGVbXQ ==.这就是我们所知道的.
The requested URL /update-cache/c/s/www.toptrouwen.nl/artikelen/132/het-uitwisselen-van-de-trouwringen-hoe-voorkom-je-bloopers/amp?amp_action=flush%26amp_ts=1523094395%26amp_url_signature=U3lzdGVrLkJ1dGVbXQ== was not found on this server. That’s all we know.
我的代码:
Dim ampBaseUrl As String = "https://www-toptrouwen-nl.cdn.ampproject.org"
Dim signatureUrl As String = "/update-cache/c/s/www.toptrouwen.nl/artikelen/132/het-uitwisselen-van-de-trouwringen-hoe-voorkom-je-bloopers/amp?amp_action=flush&_ts=" + tStamp
Dim rsa As RSA = certificate.GetRSAPrivateKey()
Dim data() As Byte = System.Text.Encoding.ASCII.GetBytes(signatureUrl)
Dim sig() As Byte = rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)
Dim AMPURLSignature As String = EncodeTo64(sig.ToString)
Dim url As String = ampBaseUrl + signatureUrl + "&_url_signature=" + AMPURLSignature
ltStatus.Text = "AMP URL:<a target='_blank' href='" + url + "'>" + url + "</a>"
此外,我确定此页面存在于Google AMP缓存中,因为我可以在移动设备上的Google搜索结果中看到并请求它.
Also, I'm sure this page exists in Google AMP cache, since I can see and request it in Google's search results on my mobile device.
更新4
I'm getting close I think and also getting some extra help, see here: https://github.com/ampproject/amphtml/issues/14483#issuecomment-380549060
我现在正在尝试使其他人也更容易进行测试:现在,我不再依赖我的SSL,而是运行以下命令来获取公钥和私钥
What I'm trying now to make it easier for others to test as well: Instead of depending on my SSL I now ran the following commands to get a public and private key
openssl genrsa 2048 > private-key.pem
openssl rsa -in private-key.pem -pubout >public-key.pem
我现在有文件private-key.pem
和public-key.pem
我将public-key.pem
重命名为apikey.pub
并将其放置在https://example.com/.well-known/amphtml/apikey.pub
I'll rename public-key.pem
to apikey.pub
and place that on https://example.com/.well-known/amphtml/apikey.pub
我想采用@CodeFuller建议的最简单方法,并创建一个.pfx
文件,然后可以将其加载到类型为X509Certificate2
的变量中.
当我运行此命令时:
openssl pkcs12 -export -out keys.pfx -inkey private-key.pem -in public-key.pem
I want to take the easiest approach recommended by @CodeFuller and create a .pfx
file that I can then load into a variable of type X509Certificate2
.
When I run this command:
openssl pkcs12 -export -out keys.pfx -inkey private-key.pem -in public-key.pem
我收到错误:unable to load certificates
但是这次我没有.crt
文件,只有public-key.pem
.如何获取.pfx
文件?我已经在此处选中了.
But this time I don't have a .crt
file, only a public-key.pem
. How can I get a .pfx
file? I already checked here.
推荐答案
我将从SSL提供商收到的证书代码保存为文件 test-private-key.cer
I saved certificate code I received from my SSL provider as file test-private-key.cer
...
证书代码
----- BEGIN证书-----
MIIF(...)DXuJ
-----结束证书-----
-----BEGIN CERTIFICATE-----
MIIF (...) DXuJ
-----END CERTIFICATE-----
以某种格式存储的文件
-----BEGIN CERTIFICATE-----
MIIF (...) DXuJ
-----END CERTIFICATE-----
是基本上包含公共密钥的证书.它不包含一个>私钥.这就是为什么从此类文件创建X509Certificate2
实例时,其HasPrivateKey
属性设置为False
且PrivateKey
返回Nothing
的原因,并且以下语句预期会抛出NullReferenceException
:
is a certificate which basically contains a public key. It does not contain private key. That's why when you create instance of X509Certificate2
from such file, it's HasPrivateKey
property is set to False
and PrivateKey
returns Nothing
, and following statement expectedly throws NullReferenceException
:
privateCert.PrivateKey.ToXmlString(True)
要对数据进行签名,您需要一个私钥.私钥具有以下格式
In order to sign the data, you need a private key. Private keys have the following format
-----BEGIN RSA PRIVATE KEY-----
MIICXQ...B7Bou+
-----END RSA PRIVATE KEY-----
此类私钥通常存储在* .key或* .pem(隐私增强邮件)文件中. 没有内置的方法可以从pem文件中加载X509Certificate2
的实例.有很多代码示例可用,您可以在上面链接的问题中找到它们.但是,最简单的解决方案是创建pfx文件(包含私钥和公钥).然后,您可以使用X509Certificate2
的相应构造函数轻松加载pfx.
Such private keys are usually stored in *.key or *.pem (Privacy Enhanced Mail) files. There is no built-in way to load instance of X509Certificate2
from pem file. There are a lot of code samples available how to do it, you will find them in the question linked above. However the easiest solution will be to create pfx file (containing both private and public keys). Then you could easily load pfx with corresponding constructor of X509Certificate2
.
使用SSL工具非常容易创建pfx文件.如果private.key
包含私钥(-----BEGIN RSA PRIVATE KEY-----
),并且public.crt
包含公钥(-----BEGIN CERTIFICATE-----
),则您可以使用以下命令创建pfx文件:
Creation of pfx file is very easy with SSL tool. If private.key
contains private key (-----BEGIN RSA PRIVATE KEY-----
) and public.crt
contains public key (-----BEGIN CERTIFICATE-----
), they you could create pfx file with the following command:
openssl pkcs12-导出-out keys.pfx -inkey private.key -in public.crt
openssl pkcs12 -export -out keys.pfx -inkey private.key -in public.crt
将要求您输入密码.当您将密钥加载到X509Certificate2
时,也会使用该密码:
You will be asked to enter the password. This password will also be used when you load the key to X509Certificate2
:
Dim certificate As X509Certificate2 = New X509Certificate2("d:\CodeFuller\_days\2018.04.05\keys.pfx", "Password here")
现在HasPrivateKey
属性设置为True
,并且PrivateKey
返回RSACryptoServiceProvider
的实例.
Now HasPrivateKey
property is set to True
and PrivateKey
returns the instance of RSACryptoServiceProvider
.
更新
关于此代码:
'Round-trip the key to XML and back, there might be a better way but this works
Dim key As RSACryptoServiceProvider = New
RSACryptoServiceProvider
key.FromXmlString(privateCert.PrivateKey.ToXmlString(True))
RSACryptoServiceProvider
的实例实际上存储在certificate.PrivateKey
中,因此您可以避免上面的代码并将其替换为:
The instance of RSACryptoServiceProvider
is actually stored in certificate.PrivateKey
so you could avoid above code and replace it with:
Dim provider As RSACryptoServiceProvider = certificate.PrivateKey
但是您当前的SignData()
通话将无法正常工作
However your current SignData()
call will not work:
Dim sig() As Byte = key.SignData(data, CryptoConfig.MapNameToOID("SHA256"))
这将引发以下异常:
System.Security.Cryptography.CryptographicException:'无效 指定的算法.
System.Security.Cryptography.CryptographicException: 'Invalid algorithm specified.'
根本原因是RSACryptoServiceProvider
不支持SHA256 .这就是为什么我建议通过以下方式用RSACng
替换它:
The root cause is that RSACryptoServiceProvider
does not support SHA256. That's why I suggest replacing it with RSACng
in the following way:
Dim rsa As RSA = certificate.GetRSAPrivateKey()
Dim data() As Byte = System.Text.Encoding.Unicode.GetBytes(signatureUrl)
Dim sig() As Byte = rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)
方法GetRSAPrivateKey
仅从.NET Framework 4.6开始才添加到X509Certificate2
类,因此,如果出现以下错误,请考虑升级:
Method GetRSAPrivateKey
was added to X509Certificate2
class only since .NET Framework 4.6, so consider upgrading if you get following error:
错误BC30456:"GetRSAPrivateKey"不是以下成员 'X509Certificate2'
error BC30456: 'GetRSAPrivateKey' is not a member of 'X509Certificate2'
更新2 (关于URL验证)
您引用的页面包含用于验证签名的openssl
命令:
The page you referenced contains openssl
command for verifying the signature:
openssl dgst -sha256 -signature signature.bin-验证public-key.pem url.txt
openssl dgst -sha256 -signature signature.bin -verify public-key.pem url.txt
但是,在您的情况下,这只是一个健全性检查,因为您刚刚使用有效过程生成了签名.因此,回答您的问题:
However in your case it will be just a sanity check, because you have just generated the signature with a valid procedure. So answering your question:
如何检查我现在结尾的URL是否有效?
How can I check whether the URL I now end up with is valid?
最好的检查就是将请求发送到带有签名URL的AMP Cache并检查响应.我以前从未使用过AMP Cache,但我相信如果签名无效,它将响应一些HTTP错误.
The best check is just to send request to AMP Cache with signed URL and check the response. I haven't used AMP Cache before but I believe it will respond with some HTTP error if the signature is invalid.
更新3 (关于签名验证失败)
UPDATE 3 (regarding failed signature verification)
更新AMP内容"页面包含用于签名URL的以下命令行:
Update AMP Content page contains following command line for signing the URL:
echo -n> url.txt '/update-cache/c/s/example.com/article?amp_action=flush&_ts=1484941817' 猫url.txt | openssl dgst -sha256 -sign private-key.pem> signature.bin
echo -n >url.txt '/update-cache/c/s/example.com/article?amp_action=flush&_ts=1484941817' cat url.txt | openssl dgst -sha256 -sign private-key.pem >signature.bin
我已将此命令构建的结果签名与答案中的代码计算出的签名进行了比较.事实证明它们是不同的.我研究了可能的根本原因,发现问题是由我们获取URL字节的方式引起的.当前是:
I have compared result signature built by this command with the signature calculated by the code from my answer. It turned out that they differ. I have researched the possible root cause and found that the problem is caused by the way we get URL bytes. Currently it's:
Dim data() As Byte = System.Text.Encoding.Unicode.GetBytes(signatureUrl)
但是,我们应该对以ASCII表示的URL进行签名.因此,将上面的行替换为:
However we should sign the URL represented in ASCII. So replace above line with:
Dim data() As Byte = System.Text.Encoding.ASCII.GetBytes(signatureUrl)
现在,openssl实用程序和上面的代码中的两个签名都匹配.如果在修复后仍从Google AMP获得URL signature verification error
,则问题出在传递用于签名的输入URL.
Now both signatures, from openssl utility and the code above, matches. If after the fix you still get URL signature verification error
from Google AMP, then the problem will be with the input URL passed for signing.
UPDATE 4 (从私钥和公钥获取PFX)
UPDATE 4 (Getting PFX from private and public keys)
生成私钥:
openssl genrsa 2048> private-key.pem
openssl genrsa 2048 > private-key.pem
生成公共密钥:
openssl rsa -in private-key.pem -pubout> public-key.pem
openssl rsa -in private-key.pem -pubout > public-key.pem
创建证书签名请求:
openssl req -new -key private-key.pem -out certificate.csr
openssl req -new -key private-key.pem -out certificate.csr
创建证书:
openssl x509 -req -days 365 -in certificate.csr -signkey private-key.pem -out public.crt
openssl x509 -req -days 365 -in certificate.csr -signkey private-key.pem -out public.crt
此处将要求您输入一些证书字段,例如国家名称,组织名称等.使用哪个值并不重要,因为出于测试目的需要此证书.
You will be asked here for some certificate fields, e.g. Country Name, Organization Name, etc. It does not really matter which values you use, since you need this certificate for test purposes.
创建pfx文件:
openssl pkcs12-导出-out keys.pfx -inkey private-key.pem -in public.crt
openssl pkcs12 -export -out keys.pfx -inkey private-key.pem -in public.crt
这篇关于ASP.NET:使用文件中的公钥和私钥对URL进行签名的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!