将 RSA 加密从 Javascript 转换为 PHP
Converting RSA Encryption from Javascript to PHP
我正在尝试登录一个使用 Javascript RSA 加密明文密码的网站 (Steam),以便在 POST 中发送密文请求作为参数。我无法正确地将 Javascript RSA 从 Javascript 转换为 PHP。
当我尝试将使用我的任何 PHP 脚本创建的密文密码发送到该网站时,我收到错误的登录信息,表明我的加密过程中某处不正确。
从浏览器发送实际请求并使用Fiddler记录请求时,模数的位长始终与密文的位长相同。这也可以从 Javascript 函数 pkcs1pad2 中推导出来。这是我在尝试检查加密是否正确时寻找的标准之一。
使用相同的 public 密钥和明文不会总是产生与使用 pkcs1 时相同的密文,随机字节被填充到明文的开头,直到长度与模数相同。因此无法与通过浏览器获得的正确密文进行比较。
做Javascript的modPowInt($exponent, $modulus)和PHP的modPow($exponent, $modulus)执行不同的计算作为PHP RSA (2)虽然看起来和Javascript RSA?
一模一样,但不起作用
Javascript RSA - 什么需要转换成PHP
var RSAPublicKey = function($modulus_hex, $encryptionExponent_hex) {
this.modulus = new BigInteger( $modulus_hex, 16);
this.encryptionExponent = new BigInteger( $encryptionExponent_hex, 16);
};
var Base64 = {
base64: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
encode: function($input) {
if (!$input) {
return false;
}
var $output = "";
var $chr1, $chr2, $chr3;
var $enc1, $enc2, $enc3, $enc4;
var $i = 0;
do {
$chr1 = $input.charCodeAt($i++);
$chr2 = $input.charCodeAt($i++);
$chr3 = $input.charCodeAt($i++);
$enc1 = $chr1 >> 2;
$enc2 = (($chr1 & 3) << 4) | ($chr2 >> 4);
$enc3 = (($chr2 & 15) << 2) | ($chr3 >> 6);
$enc4 = $chr3 & 63;
if (isNaN($chr2)) $enc3 = $enc4 = 64;
else if (isNaN($chr3)) $enc4 = 64;
$output += this.base64.charAt($enc1) + this.base64.charAt($enc2) + this.base64.charAt($enc3) + this.base64.charAt($enc4);
} while ($i < $input.length);
return $output;
},
decode: function($input) {
if(!$input) return false;
$input = $input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
var $output = "";
var $enc1, $enc2, $enc3, $enc4;
var $i = 0;
do {
$enc1 = this.base64.indexOf($input.charAt($i++));
$enc2 = this.base64.indexOf($input.charAt($i++));
$enc3 = this.base64.indexOf($input.charAt($i++));
$enc4 = this.base64.indexOf($input.charAt($i++));
$output += String.fromCharCode(($enc1 << 2) | ($enc2 >> 4));
if ($enc3 != 64) $output += String.fromCharCode((($enc2 & 15) << 4) | ($enc3 >> 2));
if ($enc4 != 64) $output += String.fromCharCode((($enc3 & 3) << 6) | $enc4);
} while ($i < $input.length);
return $output;
}
};
var Hex = {
hex: "0123456789abcdef",
encode: function($input) {
if(!$input) return false;
var $output = "";
var $k;
var $i = 0;
do {
$k = $input.charCodeAt($i++);
$output += this.hex.charAt(($k >> 4) &0xf) + this.hex.charAt($k & 0xf);
} while ($i < $input.length);
return $output;
},
decode: function($input) {
if(!$input) return false;
$input = $input.replace(/[^0-9abcdef]/g, "");
var $output = "";
var $i = 0;
do {
$output += String.fromCharCode(((this.hex.indexOf($input.charAt($i++)) << 4) & 0xf0) | (this.hex.indexOf($input.charAt($i++)) & 0xf));
} while ($i < $input.length);
return $output;
}
};
var RSA = {
getPublicKey: function( $modulus_hex, $exponent_hex ) {
return new RSAPublicKey( $modulus_hex, $exponent_hex );
},
encrypt: function($data, $pubkey) {
if (!$pubkey) return false;
$data = this.pkcs1pad2($data,($pubkey.modulus.bitLength()+7)>>3);
if(!$data) return false;
$data = $data.modPowInt($pubkey.encryptionExponent, $pubkey.modulus);
if(!$data) return false;
$data = $data.toString(16);
if(($data.length & 1) == 1)
$data = "0" + $data;
return Base64.encode(Hex.decode($data));
},
pkcs1pad2: function($data, $keysize) {
if($keysize < $data.length + 11)
return null;
var $buffer = [];
var $i = $data.length - 1;
while($i >= 0 && $keysize > 0)
$buffer[--$keysize] = $data.charCodeAt($i--);
$buffer[--$keysize] = 0;
while($keysize > 2)
$buffer[--$keysize] = Math.floor(Math.random()*254) + 1;
$buffer[--$keysize] = 2;
$buffer[--$keysize] = 0;
return new BigInteger($buffer);
}
};
PHP RSA (1)
这是我解决加密问题的初步尝试。密文的位长与模数一致(2048。但是密文导致登录错误。
include 'phpseclib/Math/BigInteger.php';
include 'phpseclib/Crypt/RSA.php';
function encrypt($data, $mod, $exp)
{
$rsa = new Crypt_RSA();
$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
$rsa->publicExponent = new Math_BigInteger($exp, 16);
$rsa->modulus = new Math_BigInteger($mod, 16);
$rsa->publicKeyFormat = CRYPT_RSA_PUBLIC_FORMAT_PKCS1;
$rsa->loadKey($rsa->getPublicKey());
return urlencode(base64_encode($rsa->encrypt($data)));
}
PHP RSA (2)
我尝试将 Javascript RSA 脚本转换为 PHP。位长与模数不一致(2472 不是 2048)
include 'phpseclib/Math/BigInteger.php';
include 'phpseclib/Crypt/RSA.php';
function encrypt($data, $mod, $exp){
$mod = new Math_BigInteger($mod,16);
$exp = new Math_BigInteger($exp,16);
if($exp == null || $mod == null) return false;
$data = pkcs1pad2($data, (strlen($mod->toBits())+7)>>3);
if($data == null) return false;
$data = $data->modPow($exp,$mod);
if($data == null) return false;
$data = $data->toString();
if((strlen($data) & 1) == 1)
$data = "0" . $data;
return urlencode(base64_encode(hex2bin($data)));
}
function pkcs1pad2($data, $keysize){
if($keysize < strlen($data) + 11)
return null;
$buffer = array();
$i = strlen($data)-1;
while($i >= 0 && $keysize > 0)
$buffer[--$keysize] = $data[$i--];
$buffer[--$keysize] = 0;
while($keysize > 2)
$buffer[--$keysize] = rand(0,255);
$buffer[--$keysize] = 2;
$buffer[--$keysize] = 0;
return new Math_BigInteger(bin2hex(implode('',$buffer)), 16);
}
PHP RSA (3)
这是我的代码的最后一次迭代。我试图填充缺失的位以使位长与模数 (2048) 的位长相同并成功了。但是网站仍然认为密文密码不正确。
include('Crypt/RSA.php');
include('Math/BigInteger.php');
function hex_to_binary($hex) {
$binary = '';
for ($i = 0; $i < strlen($hex); $i += 2) {
$hexChunk = substr($hex, $i, 2);
$intChunk = hexdec($hexChunk);
$binaryChunk = decbin($intChunk);
$binaryChunk = str_pad($binaryChunk, 8, '0', STR_PAD_LEFT);
$binary .= $binaryChunk;
}
return $binary;
}
function bytes_to_binary($bytes) {
$binary = '';
foreach($bytes as $integer) {
$byte = decbin($integer);
$byte = str_pad($byte, 8, '0', STR_PAD_LEFT);
$binary .= $byte;
}
return $binary;
}
function binary_to_text($binary) {
$text = '';
$binaryLength = strlen($binary);
for ($i = 0; $i < $binaryLength; $i += 8) {
$binaryChunk = substr($binary, $i, 8);
$characterCode = bindec($binaryChunk);
$character = chr($characterCode);
$text .= $character;
}
return $text;
}
function getPublicKey($modulusHex, $exponentHex) {
$publicKey = Array(
'modulus' => $modulusHex,
'exponent' => $exponentHex
);
return $publicKey;
}
function pkcs1pad2($data, $publicKey) {
// Convert Modulus from Hex to Binary
$modulusBinary = hex_to_binary($publicKey['modulus']);
// Get Bytes of Modulus
$modulusInteger = new Math_BigInteger($modulusBinary, 2);
$modulusBytes = ceil(strlen($modulusInteger->toBits()) / 8);
// Bytes in the Modulus must be 11 Bytes longer than Bytes in the Password (UTF-8 => 8 Bytes per Character)
if($modulusBytes < strlen($data) + 11) {
// Otherwise Encryption is impossible so Return Null
return null;
};
// Array to Store Sequence of Bytes in the Padded Password
$buffer = array();
// Variables to Hold Current Position of Bytes and Characters
$currentModulusByte = $modulusBytes;
$currentDataCharacter = strlen($data) - 1;
// Insert Password into End of Buffer
while($currentDataCharacter >= 0 && $currentModulusByte > 0) {
$buffer[--$currentModulusByte] = ord($data[$currentModulusByte--]);
};
// Insert 0 as the Next Last Value of Buffer
$Buffer[--$currentModulusByte] = 0;
// Insert Random Bytes into Buffer until the First 2 Bytes
while($currentModulusByte > 2) {
$buffer[--$currentModulusByte] = rand(1,255);
};
// Insert 0 and 2 as the First 2 Bytes in the Sequence
$buffer[--$currentModulusByte] = 2;
$buffer[--$currentModulusByte] = 0;
// Math_BigInteger() doesn't accept an Array of Bytes so convert it to a Binary string
$paddedModulusBinary = bytes_to_binary($buffer);
// Convert Binary to BigInteger using a Base 2
$paddedModulusInteger = new Math_BigInteger($paddedModulusBinary, 2);
return $paddedModulusInteger;
}
// Copy of the Encrypt function
function encrypt($data, $publicKey) {
// Make Sure that the Public Key is not Null
if(!$publicKey) {
return false;
};
// Pad the Data for Encryption
$paddedData = pkcs1pad2($data, $publicKey);
// Make Sure that the Padded Data is not Null
if(!$paddedData) {
return false;
};
// Encrypt the Padded Data using the Public Key
$exponentBinary = hex_to_binary($publicKey['exponent']);
$exponentBigInt = new Math_BigInteger($exponentBinary, 2);
$modulusBinary = hex_to_binary($publicKey['modulus']);
$modulusBigInt = new Math_BigInteger($modulusBinary, 2);
$encryptedData = $paddedData->modPow($exponentBigInt, $modulusBigInt);
// Make Sure that the Encrypted Data is not Null
if(!$encryptedData) {
return false;
}
// Convert the Encrypted Data to Binary
$encryptedBinaryData = $encryptedData->toBits();
// Pad Empty Bits onto the Start of the Encrypted Binary Data
$modulusBitLength = strlen($publicKey['modulus']) * 4;
$encryptedBinaryData = str_pad($encryptedBinaryData, $modulusBitLength, '0', STR_PAD_LEFT);
// Convert Binary to Text
$textData = binary_to_text($encryptedBinaryData);
// Encode Binary with Base64
$base64EncodedData = base64_encode($textData);
// Encode Base64 for Url
$urlEncodedData = urlencode($base64EncodedData);
return $urlEncodedData;
}
任何帮助都将不胜感激。
这个问题过去曾提出过,但从未得到妥善回答。希望这次我们能有更多的运气。
与其推出自己的解决方案,不如考虑使用 openssl_{public,private}_{encrypt,decrypt}
函数。
我的第一个想法...您可能需要做 define('CRYPT_RSA_PKCS15_COMPAT', true)
。您在 PHP 端使用的 phpseclib 使用 PKCS1 v2.0+ 中定义的填充技术,它与 PKCS1 v1.5 中描述的填充技术略有不同。在进行加密之前执行 define('CRYPT_RSA_PKCS15_COMPAT', true)
可能会有所帮助。
否则,可能值得尝试 $rsa->publicExponent = new Math_BigInteger($exp, -16)
下班回家后,我会尝试更多地玩这个..
我正在尝试登录一个使用 Javascript RSA 加密明文密码的网站 (Steam),以便在 POST 中发送密文请求作为参数。我无法正确地将 Javascript RSA 从 Javascript 转换为 PHP。
当我尝试将使用我的任何 PHP 脚本创建的密文密码发送到该网站时,我收到错误的登录信息,表明我的加密过程中某处不正确。
从浏览器发送实际请求并使用Fiddler记录请求时,模数的位长始终与密文的位长相同。这也可以从 Javascript 函数 pkcs1pad2 中推导出来。这是我在尝试检查加密是否正确时寻找的标准之一。
使用相同的 public 密钥和明文不会总是产生与使用 pkcs1 时相同的密文,随机字节被填充到明文的开头,直到长度与模数相同。因此无法与通过浏览器获得的正确密文进行比较。
做Javascript的modPowInt($exponent, $modulus)和PHP的modPow($exponent, $modulus)执行不同的计算作为PHP RSA (2)虽然看起来和Javascript RSA?
一模一样,但不起作用Javascript RSA - 什么需要转换成PHP
var RSAPublicKey = function($modulus_hex, $encryptionExponent_hex) {
this.modulus = new BigInteger( $modulus_hex, 16);
this.encryptionExponent = new BigInteger( $encryptionExponent_hex, 16);
};
var Base64 = {
base64: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
encode: function($input) {
if (!$input) {
return false;
}
var $output = "";
var $chr1, $chr2, $chr3;
var $enc1, $enc2, $enc3, $enc4;
var $i = 0;
do {
$chr1 = $input.charCodeAt($i++);
$chr2 = $input.charCodeAt($i++);
$chr3 = $input.charCodeAt($i++);
$enc1 = $chr1 >> 2;
$enc2 = (($chr1 & 3) << 4) | ($chr2 >> 4);
$enc3 = (($chr2 & 15) << 2) | ($chr3 >> 6);
$enc4 = $chr3 & 63;
if (isNaN($chr2)) $enc3 = $enc4 = 64;
else if (isNaN($chr3)) $enc4 = 64;
$output += this.base64.charAt($enc1) + this.base64.charAt($enc2) + this.base64.charAt($enc3) + this.base64.charAt($enc4);
} while ($i < $input.length);
return $output;
},
decode: function($input) {
if(!$input) return false;
$input = $input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
var $output = "";
var $enc1, $enc2, $enc3, $enc4;
var $i = 0;
do {
$enc1 = this.base64.indexOf($input.charAt($i++));
$enc2 = this.base64.indexOf($input.charAt($i++));
$enc3 = this.base64.indexOf($input.charAt($i++));
$enc4 = this.base64.indexOf($input.charAt($i++));
$output += String.fromCharCode(($enc1 << 2) | ($enc2 >> 4));
if ($enc3 != 64) $output += String.fromCharCode((($enc2 & 15) << 4) | ($enc3 >> 2));
if ($enc4 != 64) $output += String.fromCharCode((($enc3 & 3) << 6) | $enc4);
} while ($i < $input.length);
return $output;
}
};
var Hex = {
hex: "0123456789abcdef",
encode: function($input) {
if(!$input) return false;
var $output = "";
var $k;
var $i = 0;
do {
$k = $input.charCodeAt($i++);
$output += this.hex.charAt(($k >> 4) &0xf) + this.hex.charAt($k & 0xf);
} while ($i < $input.length);
return $output;
},
decode: function($input) {
if(!$input) return false;
$input = $input.replace(/[^0-9abcdef]/g, "");
var $output = "";
var $i = 0;
do {
$output += String.fromCharCode(((this.hex.indexOf($input.charAt($i++)) << 4) & 0xf0) | (this.hex.indexOf($input.charAt($i++)) & 0xf));
} while ($i < $input.length);
return $output;
}
};
var RSA = {
getPublicKey: function( $modulus_hex, $exponent_hex ) {
return new RSAPublicKey( $modulus_hex, $exponent_hex );
},
encrypt: function($data, $pubkey) {
if (!$pubkey) return false;
$data = this.pkcs1pad2($data,($pubkey.modulus.bitLength()+7)>>3);
if(!$data) return false;
$data = $data.modPowInt($pubkey.encryptionExponent, $pubkey.modulus);
if(!$data) return false;
$data = $data.toString(16);
if(($data.length & 1) == 1)
$data = "0" + $data;
return Base64.encode(Hex.decode($data));
},
pkcs1pad2: function($data, $keysize) {
if($keysize < $data.length + 11)
return null;
var $buffer = [];
var $i = $data.length - 1;
while($i >= 0 && $keysize > 0)
$buffer[--$keysize] = $data.charCodeAt($i--);
$buffer[--$keysize] = 0;
while($keysize > 2)
$buffer[--$keysize] = Math.floor(Math.random()*254) + 1;
$buffer[--$keysize] = 2;
$buffer[--$keysize] = 0;
return new BigInteger($buffer);
}
};
PHP RSA (1) 这是我解决加密问题的初步尝试。密文的位长与模数一致(2048。但是密文导致登录错误。
include 'phpseclib/Math/BigInteger.php';
include 'phpseclib/Crypt/RSA.php';
function encrypt($data, $mod, $exp)
{
$rsa = new Crypt_RSA();
$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
$rsa->publicExponent = new Math_BigInteger($exp, 16);
$rsa->modulus = new Math_BigInteger($mod, 16);
$rsa->publicKeyFormat = CRYPT_RSA_PUBLIC_FORMAT_PKCS1;
$rsa->loadKey($rsa->getPublicKey());
return urlencode(base64_encode($rsa->encrypt($data)));
}
PHP RSA (2) 我尝试将 Javascript RSA 脚本转换为 PHP。位长与模数不一致(2472 不是 2048)
include 'phpseclib/Math/BigInteger.php';
include 'phpseclib/Crypt/RSA.php';
function encrypt($data, $mod, $exp){
$mod = new Math_BigInteger($mod,16);
$exp = new Math_BigInteger($exp,16);
if($exp == null || $mod == null) return false;
$data = pkcs1pad2($data, (strlen($mod->toBits())+7)>>3);
if($data == null) return false;
$data = $data->modPow($exp,$mod);
if($data == null) return false;
$data = $data->toString();
if((strlen($data) & 1) == 1)
$data = "0" . $data;
return urlencode(base64_encode(hex2bin($data)));
}
function pkcs1pad2($data, $keysize){
if($keysize < strlen($data) + 11)
return null;
$buffer = array();
$i = strlen($data)-1;
while($i >= 0 && $keysize > 0)
$buffer[--$keysize] = $data[$i--];
$buffer[--$keysize] = 0;
while($keysize > 2)
$buffer[--$keysize] = rand(0,255);
$buffer[--$keysize] = 2;
$buffer[--$keysize] = 0;
return new Math_BigInteger(bin2hex(implode('',$buffer)), 16);
}
PHP RSA (3) 这是我的代码的最后一次迭代。我试图填充缺失的位以使位长与模数 (2048) 的位长相同并成功了。但是网站仍然认为密文密码不正确。
include('Crypt/RSA.php');
include('Math/BigInteger.php');
function hex_to_binary($hex) {
$binary = '';
for ($i = 0; $i < strlen($hex); $i += 2) {
$hexChunk = substr($hex, $i, 2);
$intChunk = hexdec($hexChunk);
$binaryChunk = decbin($intChunk);
$binaryChunk = str_pad($binaryChunk, 8, '0', STR_PAD_LEFT);
$binary .= $binaryChunk;
}
return $binary;
}
function bytes_to_binary($bytes) {
$binary = '';
foreach($bytes as $integer) {
$byte = decbin($integer);
$byte = str_pad($byte, 8, '0', STR_PAD_LEFT);
$binary .= $byte;
}
return $binary;
}
function binary_to_text($binary) {
$text = '';
$binaryLength = strlen($binary);
for ($i = 0; $i < $binaryLength; $i += 8) {
$binaryChunk = substr($binary, $i, 8);
$characterCode = bindec($binaryChunk);
$character = chr($characterCode);
$text .= $character;
}
return $text;
}
function getPublicKey($modulusHex, $exponentHex) {
$publicKey = Array(
'modulus' => $modulusHex,
'exponent' => $exponentHex
);
return $publicKey;
}
function pkcs1pad2($data, $publicKey) {
// Convert Modulus from Hex to Binary
$modulusBinary = hex_to_binary($publicKey['modulus']);
// Get Bytes of Modulus
$modulusInteger = new Math_BigInteger($modulusBinary, 2);
$modulusBytes = ceil(strlen($modulusInteger->toBits()) / 8);
// Bytes in the Modulus must be 11 Bytes longer than Bytes in the Password (UTF-8 => 8 Bytes per Character)
if($modulusBytes < strlen($data) + 11) {
// Otherwise Encryption is impossible so Return Null
return null;
};
// Array to Store Sequence of Bytes in the Padded Password
$buffer = array();
// Variables to Hold Current Position of Bytes and Characters
$currentModulusByte = $modulusBytes;
$currentDataCharacter = strlen($data) - 1;
// Insert Password into End of Buffer
while($currentDataCharacter >= 0 && $currentModulusByte > 0) {
$buffer[--$currentModulusByte] = ord($data[$currentModulusByte--]);
};
// Insert 0 as the Next Last Value of Buffer
$Buffer[--$currentModulusByte] = 0;
// Insert Random Bytes into Buffer until the First 2 Bytes
while($currentModulusByte > 2) {
$buffer[--$currentModulusByte] = rand(1,255);
};
// Insert 0 and 2 as the First 2 Bytes in the Sequence
$buffer[--$currentModulusByte] = 2;
$buffer[--$currentModulusByte] = 0;
// Math_BigInteger() doesn't accept an Array of Bytes so convert it to a Binary string
$paddedModulusBinary = bytes_to_binary($buffer);
// Convert Binary to BigInteger using a Base 2
$paddedModulusInteger = new Math_BigInteger($paddedModulusBinary, 2);
return $paddedModulusInteger;
}
// Copy of the Encrypt function
function encrypt($data, $publicKey) {
// Make Sure that the Public Key is not Null
if(!$publicKey) {
return false;
};
// Pad the Data for Encryption
$paddedData = pkcs1pad2($data, $publicKey);
// Make Sure that the Padded Data is not Null
if(!$paddedData) {
return false;
};
// Encrypt the Padded Data using the Public Key
$exponentBinary = hex_to_binary($publicKey['exponent']);
$exponentBigInt = new Math_BigInteger($exponentBinary, 2);
$modulusBinary = hex_to_binary($publicKey['modulus']);
$modulusBigInt = new Math_BigInteger($modulusBinary, 2);
$encryptedData = $paddedData->modPow($exponentBigInt, $modulusBigInt);
// Make Sure that the Encrypted Data is not Null
if(!$encryptedData) {
return false;
}
// Convert the Encrypted Data to Binary
$encryptedBinaryData = $encryptedData->toBits();
// Pad Empty Bits onto the Start of the Encrypted Binary Data
$modulusBitLength = strlen($publicKey['modulus']) * 4;
$encryptedBinaryData = str_pad($encryptedBinaryData, $modulusBitLength, '0', STR_PAD_LEFT);
// Convert Binary to Text
$textData = binary_to_text($encryptedBinaryData);
// Encode Binary with Base64
$base64EncodedData = base64_encode($textData);
// Encode Base64 for Url
$urlEncodedData = urlencode($base64EncodedData);
return $urlEncodedData;
}
任何帮助都将不胜感激。
这个问题过去曾提出过,但从未得到妥善回答。希望这次我们能有更多的运气。
与其推出自己的解决方案,不如考虑使用 openssl_{public,private}_{encrypt,decrypt}
函数。
我的第一个想法...您可能需要做 define('CRYPT_RSA_PKCS15_COMPAT', true)
。您在 PHP 端使用的 phpseclib 使用 PKCS1 v2.0+ 中定义的填充技术,它与 PKCS1 v1.5 中描述的填充技术略有不同。在进行加密之前执行 define('CRYPT_RSA_PKCS15_COMPAT', true)
可能会有所帮助。
否则,可能值得尝试 $rsa->publicExponent = new Math_BigInteger($exp, -16)
下班回家后,我会尝试更多地玩这个..