如何在服务器端使用PHP生成的public密钥?
How to use the public key generated by using PHP on the server?
在服务器上,我有一个 PHP 脚本 (Laravel) 可以生成 RSA 密钥对,将它们闪存到会话中,并且 return base64 编码的 public 密钥,将在我的 windows 应用程序中用于加密密码,密钥是通过调用 openssl_pkey_new函数:
// get RSA key pairs
public function generateRSAKeyPairTest(Request $request)
{
$t = $this->microtime_float();
$res = openssl_pkey_new();
openssl_pkey_export($res, $privkeyraw);
$d= openssl_pkey_get_details($res);
$pubkeyraw = $d['key'];
$pubkey = base64_encode($pubkeyraw);
$privkey = base64_encode($privkeyraw);
$request->session()->flash('privkey', $privkey);
$request->session()->flash('pubkey', $pubkey);
$tdiff = $this->microtime_float() - $t;
$keypair = array('pubkey' => $pubkey,'diff' => $tdiff);
return response()->json($keypair);
}
// decode the encryted string
public function decryptRSATest(Request $request)
{
if ($request->session()->has('privkey'))
{
$privkey = $request->session()->get('privkey', 'default value');
$resPriv = openssl_pkey_get_private(base64_decode($privkey));
$encrypted_text_base64 = $request->encrypted_text;
$outval = '-';
$encrypted_text = base64_decode($encrypted_text_base64);
openssl_private_decrypt($encrypted_text, $outval, $resPriv);
return response()->json(
[
'decoded_text_from_clit'=>$outval
]
);
}
return response()->json(["error"=>"private key does not exist!"]);
}
然后在我的 windows 应用程序 (C#) 中,我从服务器获取 json 并检索 public 密钥
var data = Convert.FromBase64String(publicKeyStringBase64);
var publicKeyRaw = Encoding.UTF8.GetString(data);
var pkStr = publicKeyRaw.Replace("-----END PUBLIC KEY-----", "").Replace("-----BEGIN PUBLIC KEY-----", "");
var publicKey = Encoding.UTF8.GetBytes(pkStr);
publicKeyRaw 如下(PKCS#8 格式):
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArwg6U1EET7OSbLO7UUZh
7p8ODYY4kXUd5S1Z/qexG5IqpNflrdQbpVh+8KWNi83oidAUjWEb050Rl3AuY/E6
7hYlEdUvI9pevmBpjjU1GktzQsDsbva3THHSpTZXPlctnFnuz0b5hVu1nUETmbGF
fSbslZet3pbKcK5KGnJpm6v6OQGpvgQjyNWF16HjUD4/x1rAL2aDNOZZED+FNJcC
hNmdK1A8nECk1JoTTdiK7r0EXMWxdVjEaSkAsvi7ywKi7ZESWwS1JmRuIJ5ZPiRx
Fvur1tgaiomEZ+9oDpk1+bwDenrERYgBxw2L6Rw0CwuyinhwYfIbWkNmy4cAiZVx
KwIDAQAB
-----END PUBLIC KEY-----
我尝试使用RSAParameters,但模数是原始字节数组(数据)还是从剥离字符串转换而来的数组(publicKey),使用RSACryptoServiceProvider的Encrypt方法加密的encryptedText在服务端无法被openssl_private_decrypt解密
var rsaInfo = new RSAParameters()
{
Exponent = new byte[] { 1, 0, 1 },
Modulus = data, // publicKey
};
var csp = new RSACryptoServiceProvider();
csp.ImportParameters(rsaInfo);
var password = "testing-passsword!";
var hash = csp.Encrypt(Encoding.UTF8.GetBytes(password), false);
var encryptedText = Convert.ToBase64String(hash);
我猜想 RSACryptoServiceProvider 没有正确加密密码,那么将此密钥字符串 (publicKeyRaw ) 与 RSACryptoServiceProvider 一起使用的正确方法是什么?
[更新]
在 google 上做了很多搜索工作后,问题变得很清楚(就像@James Reinstate Monica Polk 提到的一样):
使用 openssl_pkey_new() 生成的密钥默认为 PKCS#8 格式,而 RSACryptoServiceProvider 仅接受 PKCS#1 v1 格式的密钥。 5 格式。这种不兼容导致了这个问题。
所以,你有什么解决办法吗?
我从这个 post 得到了一个解决方案:
在使用 RSA 参数 class.
时,我们需要注意很多事情
- 建议使用 public 密钥进行编码
base64_encode 在服务器上,以便它可以在 json 字符串中传递给客户端。我使用 phpseclib 而不是 openssl。
生成 PKCS#1 格式 public 密钥
- 在C#中,将base64编码的字符串解码为可读的public密钥
字符串,格式如下:
-----BEGIN RSA PUBLIC KEY-----
base64 encoded data
-----END RSA PUBLIC KEY-----
- 在 C# 中,检索 public 关键数据并提取模数和
public 指数字节数组。在这一步,我们应该支付一个
特别注意 RSAParameters class 中的 Modulus,
当我们填写模数时,我们应该跳过第一个字节,这
是 0x00。我实现了一个简单但不完整的解码器如下:
public static RSAParameters GetRSAParametersFromPublicKey(string publicKeyString)
{
// publicKeyString should have following format:
// -----BEGIN RSA PUBLIC KEY---- -
// base64 encoded data
// -----END RSA PUBLIC KEY---- -
var pubicKeyContentString = publicKeyString.Replace("-----BEGIN RSA PUBLIC KEY-----", "")
.Replace("-----END RSA PUBLIC KEY-----", "")
.Replace("\r", "")
.Replace("\n", "")
.Replace(@"\/", "/");
var publicKeyArray = Convert.FromBase64String(pubicKeyContentString);
var mask = 0x7F;
var skipCount = 0;
var rsaParameters = new RSAParameters();
for (int i = 0; i < publicKeyArray.Length; i=skipCount)
{
var tag = publicKeyArray[i];
var lengthLength = publicKeyArray[i + 1];
var length = Convert.ToInt32(lengthLength);
skipCount += 2;
if (lengthLength > mask)
{
var lengthBit = lengthLength & mask;
var lengthBytes = publicKeyArray.Skip(skipCount).Take(lengthBit).ToArray();
skipCount += lengthBit;
length = BitConverter.ToInt16(lengthBytes.Reverse().ToArray(),0);
}
if (tag == 0x02)
{
// both modulus and public exponent start with 0x02: integer
// therefore, 0x02 is the only tag we are interested in
var valueBytes = publicKeyArray.Skip(skipCount).Take(length).ToArray();
if (valueBytes[0] == 0x00)
{
// a valid DER has a leading byte 0x00
rsaParameters.Modulus = valueBytes.Skip(1).ToArray();
}
else
{
rsaParameters.Exponent = valueBytes;
}
skipCount += length;
}
}
return rsaParameters;
}
这是一个使用输入 base64 编码的 public 密钥字符串
加密输入纯文本的函数
public static string Cryptography(string plainText, string publicKeyStringBase64)
{
var publicKey = Convert.FromBase64String(publicKeyStringBase64);
var publicKeyRaw = Encoding.UTF8.GetString(publicKey);
var rsaInfo = GetRSAParametersFromPublicKey(publicKeyRaw);
if (rsaInfo.Modulus != null && rsaInfo.Exponent != null)
{
var csp = new RSACryptoServiceProvider();
csp.ImportParameters(rsaInfo);
var hash = csp.Encrypt(Encoding.Unicode.GetBytes(plainText), false);
var encryptedText = HttpUtility.UrlEncode(Convert.ToBase64String(hash));
return encryptedText;
}
return "!!error!!";
}
- 在服务器上,我想使用 phpseclib 来生成 PKCS#1 密钥对并执行解码 (laravel):
use phpseclib\Crypt\RSA;
public function decryptRSATest(Request $request)
{
if ($request->session()->has('privkey'))
{
$privkeybase64 = $request->session()->get('privkey', 'default value');
str_replace(['\/', '\n'], ['/', ''], $privkeybase64);
$privkey = base64_decode($privkeybase64);
$rsa = new RSA();
$received = str_replace(['\/', '\n',"[=13=]",'\'], ['/', '', '',""], $request->encrypted_text);
$rsa->loadKey($privkey);
$rsa->setEncryptionMode(RSA::ENCRYPTION_PKCS1);
$decrypt_text2 = $rsa->decrypt(base64_decode($received));
$decrypt_text2 = str_replace("[=13=]",'',$decrypt_text2);
return response()->json(
[
'decoded_text_from_clit' => $decrypt_text2
]
);
}
return response()->json(["error"=>"private key does not exist!"]);
}
此外,如果我们只需要模数和指数而不是完整的 public 密钥,我们可以将模数和指数传递给客户端 (base64_encoded):
// PHP - Server end
$modulus_base64 = base64_encode($rsa->modulus);
$exponent_base64 = base64_encode($rsa->exponent);
在 C# 中,正确解码它们可能有点困难,这里是一个代码片段:
// C# - Client end
var modulus = Convert.FromBase64String(modulusStringBase64);
var exponent = Convert.FromBase64String(exponentStringBase64);
var modulusRaw = Encoding.UTF8.GetString(modulus);
var exponentRaw = Encoding.UTF8.GetString(exponent);
var bigIntegerModulus = BigInteger.Parse(modulusRaw);
var bigIntegerExponent = BigInteger.Parse(exponentRaw);
var exponentArray = bigIntegerExponent.ToByteArray();
var modulusArray = bigIntegerModulus.ToByteArray();
和一些有用的在线工具:
希望这对您有所帮助。
在服务器上,我有一个 PHP 脚本 (Laravel) 可以生成 RSA 密钥对,将它们闪存到会话中,并且 return base64 编码的 public 密钥,将在我的 windows 应用程序中用于加密密码,密钥是通过调用 openssl_pkey_new函数:
// get RSA key pairs
public function generateRSAKeyPairTest(Request $request)
{
$t = $this->microtime_float();
$res = openssl_pkey_new();
openssl_pkey_export($res, $privkeyraw);
$d= openssl_pkey_get_details($res);
$pubkeyraw = $d['key'];
$pubkey = base64_encode($pubkeyraw);
$privkey = base64_encode($privkeyraw);
$request->session()->flash('privkey', $privkey);
$request->session()->flash('pubkey', $pubkey);
$tdiff = $this->microtime_float() - $t;
$keypair = array('pubkey' => $pubkey,'diff' => $tdiff);
return response()->json($keypair);
}
// decode the encryted string
public function decryptRSATest(Request $request)
{
if ($request->session()->has('privkey'))
{
$privkey = $request->session()->get('privkey', 'default value');
$resPriv = openssl_pkey_get_private(base64_decode($privkey));
$encrypted_text_base64 = $request->encrypted_text;
$outval = '-';
$encrypted_text = base64_decode($encrypted_text_base64);
openssl_private_decrypt($encrypted_text, $outval, $resPriv);
return response()->json(
[
'decoded_text_from_clit'=>$outval
]
);
}
return response()->json(["error"=>"private key does not exist!"]);
}
然后在我的 windows 应用程序 (C#) 中,我从服务器获取 json 并检索 public 密钥
var data = Convert.FromBase64String(publicKeyStringBase64);
var publicKeyRaw = Encoding.UTF8.GetString(data);
var pkStr = publicKeyRaw.Replace("-----END PUBLIC KEY-----", "").Replace("-----BEGIN PUBLIC KEY-----", "");
var publicKey = Encoding.UTF8.GetBytes(pkStr);
publicKeyRaw 如下(PKCS#8 格式):
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArwg6U1EET7OSbLO7UUZh 7p8ODYY4kXUd5S1Z/qexG5IqpNflrdQbpVh+8KWNi83oidAUjWEb050Rl3AuY/E6 7hYlEdUvI9pevmBpjjU1GktzQsDsbva3THHSpTZXPlctnFnuz0b5hVu1nUETmbGF fSbslZet3pbKcK5KGnJpm6v6OQGpvgQjyNWF16HjUD4/x1rAL2aDNOZZED+FNJcC hNmdK1A8nECk1JoTTdiK7r0EXMWxdVjEaSkAsvi7ywKi7ZESWwS1JmRuIJ5ZPiRx Fvur1tgaiomEZ+9oDpk1+bwDenrERYgBxw2L6Rw0CwuyinhwYfIbWkNmy4cAiZVx KwIDAQAB -----END PUBLIC KEY-----
我尝试使用RSAParameters,但模数是原始字节数组(数据)还是从剥离字符串转换而来的数组(publicKey),使用RSACryptoServiceProvider的Encrypt方法加密的encryptedText在服务端无法被openssl_private_decrypt解密
var rsaInfo = new RSAParameters()
{
Exponent = new byte[] { 1, 0, 1 },
Modulus = data, // publicKey
};
var csp = new RSACryptoServiceProvider();
csp.ImportParameters(rsaInfo);
var password = "testing-passsword!";
var hash = csp.Encrypt(Encoding.UTF8.GetBytes(password), false);
var encryptedText = Convert.ToBase64String(hash);
我猜想 RSACryptoServiceProvider 没有正确加密密码,那么将此密钥字符串 (publicKeyRaw ) 与 RSACryptoServiceProvider 一起使用的正确方法是什么?
[更新]
在 google 上做了很多搜索工作后,问题变得很清楚(就像@James Reinstate Monica Polk 提到的一样): 使用 openssl_pkey_new() 生成的密钥默认为 PKCS#8 格式,而 RSACryptoServiceProvider 仅接受 PKCS#1 v1 格式的密钥。 5 格式。这种不兼容导致了这个问题。
所以,你有什么解决办法吗?
我从这个 post 得到了一个解决方案:
在使用 RSA 参数 class.
时,我们需要注意很多事情- 建议使用 public 密钥进行编码 base64_encode 在服务器上,以便它可以在 json 字符串中传递给客户端。我使用 phpseclib 而不是 openssl。 生成 PKCS#1 格式 public 密钥
- 在C#中,将base64编码的字符串解码为可读的public密钥 字符串,格式如下:
-----BEGIN RSA PUBLIC KEY----- base64 encoded data -----END RSA PUBLIC KEY-----
- 在 C# 中,检索 public 关键数据并提取模数和 public 指数字节数组。在这一步,我们应该支付一个 特别注意 RSAParameters class 中的 Modulus, 当我们填写模数时,我们应该跳过第一个字节,这 是 0x00。我实现了一个简单但不完整的解码器如下:
public static RSAParameters GetRSAParametersFromPublicKey(string publicKeyString)
{
// publicKeyString should have following format:
// -----BEGIN RSA PUBLIC KEY---- -
// base64 encoded data
// -----END RSA PUBLIC KEY---- -
var pubicKeyContentString = publicKeyString.Replace("-----BEGIN RSA PUBLIC KEY-----", "")
.Replace("-----END RSA PUBLIC KEY-----", "")
.Replace("\r", "")
.Replace("\n", "")
.Replace(@"\/", "/");
var publicKeyArray = Convert.FromBase64String(pubicKeyContentString);
var mask = 0x7F;
var skipCount = 0;
var rsaParameters = new RSAParameters();
for (int i = 0; i < publicKeyArray.Length; i=skipCount)
{
var tag = publicKeyArray[i];
var lengthLength = publicKeyArray[i + 1];
var length = Convert.ToInt32(lengthLength);
skipCount += 2;
if (lengthLength > mask)
{
var lengthBit = lengthLength & mask;
var lengthBytes = publicKeyArray.Skip(skipCount).Take(lengthBit).ToArray();
skipCount += lengthBit;
length = BitConverter.ToInt16(lengthBytes.Reverse().ToArray(),0);
}
if (tag == 0x02)
{
// both modulus and public exponent start with 0x02: integer
// therefore, 0x02 is the only tag we are interested in
var valueBytes = publicKeyArray.Skip(skipCount).Take(length).ToArray();
if (valueBytes[0] == 0x00)
{
// a valid DER has a leading byte 0x00
rsaParameters.Modulus = valueBytes.Skip(1).ToArray();
}
else
{
rsaParameters.Exponent = valueBytes;
}
skipCount += length;
}
}
return rsaParameters;
}
这是一个使用输入 base64 编码的 public 密钥字符串
加密输入纯文本的函数public static string Cryptography(string plainText, string publicKeyStringBase64)
{
var publicKey = Convert.FromBase64String(publicKeyStringBase64);
var publicKeyRaw = Encoding.UTF8.GetString(publicKey);
var rsaInfo = GetRSAParametersFromPublicKey(publicKeyRaw);
if (rsaInfo.Modulus != null && rsaInfo.Exponent != null)
{
var csp = new RSACryptoServiceProvider();
csp.ImportParameters(rsaInfo);
var hash = csp.Encrypt(Encoding.Unicode.GetBytes(plainText), false);
var encryptedText = HttpUtility.UrlEncode(Convert.ToBase64String(hash));
return encryptedText;
}
return "!!error!!";
}
- 在服务器上,我想使用 phpseclib 来生成 PKCS#1 密钥对并执行解码 (laravel):
use phpseclib\Crypt\RSA;
public function decryptRSATest(Request $request)
{
if ($request->session()->has('privkey'))
{
$privkeybase64 = $request->session()->get('privkey', 'default value');
str_replace(['\/', '\n'], ['/', ''], $privkeybase64);
$privkey = base64_decode($privkeybase64);
$rsa = new RSA();
$received = str_replace(['\/', '\n',"[=13=]",'\'], ['/', '', '',""], $request->encrypted_text);
$rsa->loadKey($privkey);
$rsa->setEncryptionMode(RSA::ENCRYPTION_PKCS1);
$decrypt_text2 = $rsa->decrypt(base64_decode($received));
$decrypt_text2 = str_replace("[=13=]",'',$decrypt_text2);
return response()->json(
[
'decoded_text_from_clit' => $decrypt_text2
]
);
}
return response()->json(["error"=>"private key does not exist!"]);
}
此外,如果我们只需要模数和指数而不是完整的 public 密钥,我们可以将模数和指数传递给客户端 (base64_encoded):
// PHP - Server end
$modulus_base64 = base64_encode($rsa->modulus);
$exponent_base64 = base64_encode($rsa->exponent);
在 C# 中,正确解码它们可能有点困难,这里是一个代码片段:
// C# - Client end
var modulus = Convert.FromBase64String(modulusStringBase64);
var exponent = Convert.FromBase64String(exponentStringBase64);
var modulusRaw = Encoding.UTF8.GetString(modulus);
var exponentRaw = Encoding.UTF8.GetString(exponent);
var bigIntegerModulus = BigInteger.Parse(modulusRaw);
var bigIntegerExponent = BigInteger.Parse(exponentRaw);
var exponentArray = bigIntegerExponent.ToByteArray();
var modulusArray = bigIntegerModulus.ToByteArray();
和一些有用的在线工具:
希望这对您有所帮助。