php_rsa.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. <?php
  2. /*
  3. * Implementation of the RSA algorithm
  4. * (C) Copyright 2004 Edsko de Vries, Ireland
  5. *
  6. * Licensed under the GNU Public License (GPL)
  7. *
  8. * This implementation has been verified against [3]
  9. * (tested Java/PHP interoperability).
  10. *
  11. * References:
  12. * [1] "Applied Cryptography", Bruce Schneier, John Wiley & Sons, 1996
  13. * [2] "Prime Number Hide-and-Seek", Brian Raiter, Muppetlabs (online)
  14. * [3] "The Bouncy Castle Crypto Package", Legion of the Bouncy Castle,
  15. * (open source cryptography library for Java, online)
  16. * [4] "PKCS #1: RSA Encryption Standard", RSA Laboratories Technical Note,
  17. * version 1.5, revised November 1, 1993
  18. */
  19. /*
  20. * Functions that are meant to be used by the user of this PHP module.
  21. *
  22. * Notes:
  23. * - $key and $modulus should be numbers in (decimal) string format
  24. * - $message is expected to be binary data
  25. * - $keylength should be a multiple of 8, and should be in bits
  26. * - For rsa_encrypt/rsa_sign, the length of $message should not exceed
  27. * ($keylength / 8) - 11 (as mandated by [4]).
  28. * - rsa_encrypt and rsa_sign will automatically add padding to the message.
  29. * For rsa_encrypt, this padding will consist of random values; for rsa_sign,
  30. * padding will consist of the appropriate number of 0xFF values (see [4])
  31. * - rsa_decrypt and rsa_verify will automatically remove message padding.
  32. * - Blocks for decoding (rsa_decrypt, rsa_verify) should be exactly
  33. * ($keylength / 8) bytes long.
  34. * - rsa_encrypt and rsa_verify expect a public key; rsa_decrypt and rsa_sign
  35. * expect a private key.
  36. */
  37. /*
  38. * johnwinner modify list
  39. * 1)suport digest method in rsa_sign and rsa_sign (sha1 or md5 )
  40. * 2)rsa_sign return signature in base64 format
  41. * 3)modify rsa_verify define
  42. * support source documnet and base64 signatire input, boolean output
  43. *
  44. * all above base on Edsko de Vries' work
  45. *
  46. */
  47. /**
  48. *
  49. * @param $message // $message is expected to be binary data
  50. * @param $public_key // $modulus should be numbers in (decimal) string format
  51. * @param $modulus // $modulus should be numbers in (decimal) string format
  52. * @param $keylength // int
  53. * @return $result // result is binary data
  54. */
  55. function rsa_encrypt($message, $public_key, $modulus, $keylength)
  56. {
  57. $padded = add_PKCS1_padding($message, true, $keylength / 8);
  58. $number = binary_to_number($padded);
  59. $encrypted = pow_mod($number, $public_key, $modulus);
  60. $result = number_to_binary($encrypted, $keylength / 8);
  61. return $result;
  62. }
  63. /**
  64. *
  65. * @param $message // $message is expected to be binary data
  66. * @param $private_key // $modulus should be numbers in (decimal) string format
  67. * @param $modulus // $modulus should be numbers in (decimal) string format
  68. * @param $keylength // int
  69. */
  70. function rsa_decrypt($message, $private_key, $modulus, $keylength)
  71. {
  72. $number = binary_to_number($message);
  73. $decrypted = pow_mod($number, $private_key, $modulus);
  74. $result = number_to_binary($decrypted, $keylength / 8);
  75. return remove_PKCS1_padding($result, $keylength / 8);
  76. }
  77. /**
  78. *
  79. * @param $message // $message is expected to be binary data
  80. * @param $private_key // $modulus should be numbers in (decimal) string format
  81. * @param $modulus // $modulus should be numbers in (decimal) string format
  82. * @param $keylength // int
  83. * @param $hash_func // name of hash function, which will be used during signing
  84. * @return $result // signature String in Base64 format
  85. */
  86. function rsa_sign($message, $private_key, $modulus, $keylength,$hash_func)
  87. {
  88. //only suport sha1 or md5 digest now
  89. if (!function_exists($hash_func) && (strcmp($hash_func ,'sha1') == 0 || strcmp($hash_func,'md5') == 0))
  90. return false;
  91. $mssage_digest_info_hex = $hash_func($message);
  92. $mssage_digest_info_bin = hex2bins($mssage_digest_info_hex);
  93. $padded = add_PKCS1_padding($mssage_digest_info_bin, false, $keylength / 8);
  94. $number = binary_to_number($padded);
  95. $signed = pow_mod($number, $private_key, $modulus);
  96. $result = base64_encode($signed);
  97. return $result;
  98. }
  99. /**
  100. *
  101. * @param $message // $message is expected to be binary data
  102. * @param $private_key // $modulus should be numbers in (decimal) string format
  103. * @param $modulus // $modulus should be numbers in (decimal) string format
  104. * @param $keylength // int
  105. * @param $hash_func // name of hash function, which will be used during signing
  106. * @return boolean // true or false
  107. */
  108. function rsa_verify($document, $signature, $public_key, $modulus, $keylength,$hash_func)
  109. {
  110. //only suport sha1 or md5 digest now
  111. if (!function_exists($hash_func) && (strcmp($hash_func ,'sha1') == 0 || strcmp($hash_func,'md5') == 0))
  112. return false;
  113. $document_digest_info = $hash_func($document);
  114. $number = binary_to_number(base64_decode($signature));
  115. $decrypted = pow_mod($number, $public_key, $modulus);
  116. $decrypted_bytes = number_to_binary($decrypted, $keylength / 8);
  117. if($hash_func == "sha1" )
  118. {
  119. $result = remove_PKCS1_padding_sha1($decrypted_bytes, $keylength / 8);
  120. }
  121. else
  122. {
  123. $result = remove_PKCS1_padding_md5($decrypted_bytes, $keylength / 8);
  124. }
  125. return(hex2bins($document_digest_info) == $result);
  126. }
  127. /*
  128. * Some constants
  129. */
  130. define("BCCOMP_LARGER", 1);
  131. /*
  132. * The actual implementation.
  133. * Requires BCMath support in PHP (compile with --enable-bcmath)
  134. */
  135. //--
  136. // Calculate (p ^ q) mod r
  137. //
  138. // We need some trickery to [2]:
  139. // (a) Avoid calculating (p ^ q) before (p ^ q) mod r, because for typical RSA
  140. // applications, (p ^ q) is going to be _WAY_ too large.
  141. // (I mean, __WAY__ too large - won't fit in your computer's memory.)
  142. // (b) Still be reasonably efficient.
  143. //
  144. // We assume p, q and r are all positive, and that r is non-zero.
  145. //
  146. // Note that the more simple algorithm of multiplying $p by itself $q times, and
  147. // applying "mod $r" at every step is also valid, but is O($q), whereas this
  148. // algorithm is O(log $q). Big difference.
  149. //
  150. // As far as I can see, the algorithm I use is optimal; there is no redundancy
  151. // in the calculation of the partial results.
  152. //--
  153. function pow_mod($p, $q, $r)
  154. {
  155. // Extract powers of 2 from $q
  156. $factors = array();
  157. $div = $q;
  158. $power_of_two = 0;
  159. while(bccomp($div, "0") == BCCOMP_LARGER)
  160. {
  161. $rem = bcmod($div, 2);
  162. $div = bcdiv($div, 2);
  163. if($rem) array_push($factors, $power_of_two);
  164. $power_of_two++;
  165. }
  166. // Calculate partial results for each factor, using each partial result as a
  167. // starting point for the next. This depends of the factors of two being
  168. // generated in increasing order.
  169. $partial_results = array();
  170. $part_res = $p;
  171. $idx = 0;
  172. foreach($factors as $factor)
  173. {
  174. while($idx < $factor)
  175. {
  176. $part_res = bcpow($part_res, "2");
  177. $part_res = bcmod($part_res, $r);
  178. $idx++;
  179. }
  180. array_push($partial_results, $part_res);
  181. }
  182. // Calculate final result
  183. $result = "1";
  184. foreach($partial_results as $part_res)
  185. {
  186. $result = bcmul($result, $part_res);
  187. $result = bcmod($result, $r);
  188. }
  189. return $result;
  190. }
  191. //--
  192. // Function to add padding to a decrypted string
  193. // We need to know if this is a private or a public key operation [4]
  194. //--
  195. function add_PKCS1_padding($data, $isPublicKey, $blocksize)
  196. {
  197. $pad_length = $blocksize - 3 - strlen($data);
  198. if($isPublicKey)
  199. {
  200. $block_type = "\x02";
  201. $padding = "";
  202. for($i = 0; $i < $pad_length; $i++)
  203. {
  204. $rnd = mt_rand(1, 255);
  205. $padding .= chr($rnd);
  206. }
  207. }
  208. else
  209. {
  210. $block_type = "\x01";
  211. $padding = str_repeat("\xFF", $pad_length);
  212. }
  213. return "\x00" . $block_type . $padding . "\x00" . $data;
  214. }
  215. //--
  216. // Remove padding from a decrypted string
  217. // See [4] for more details.
  218. //--
  219. function remove_PKCS1_padding($data, $blocksize)
  220. {
  221. //assert(strlen($data) == $blocksize);
  222. $data = substr($data, 1);
  223. // We cannot deal with block type 0
  224. if($data{0} == '\0')
  225. die("Block type 0 not implemented.");
  226. // Then the block type must be 1 or 2
  227. //assert(($data{0} == "\x01") || ($data{0} == "\x02"));
  228. // Remove the padding
  229. $offset = strpos($data, "\0", 1);
  230. return substr($data, $offset + 1);
  231. }
  232. function remove_PKCS1_padding_sha1($data, $blocksize) {
  233. $digestinfo = remove_PKCS1_padding($data, $blocksize);
  234. $digestinfo_length = strlen($digestinfo);
  235. //sha1 digestinfo length not less than 20
  236. //assert($digestinfo_length >= 20);
  237. return substr($digestinfo, $digestinfo_length-20);
  238. }
  239. function remove_PKCS1_padding_md5($data, $blocksize) {
  240. $digestinfo = remove_PKCS1_padding($data, $blocksize);
  241. $digestinfo_length = strlen($digestinfo);
  242. //md5 digestinfo length not less than 16
  243. //assert($digestinfo_length >= 16);
  244. return substr($digestinfo, $digestinfo_length-16);
  245. }
  246. //--
  247. // Convert binary data to a decimal number
  248. //--
  249. function binary_to_number($data)
  250. {
  251. $base = "256";
  252. $radix = "1";
  253. $result = "0";
  254. for($i = strlen($data) - 1; $i >= 0; $i--)
  255. {
  256. $digit = ord($data{$i});
  257. $part_res = bcmul($digit, $radix);
  258. $result = bcadd($result, $part_res);
  259. $radix = bcmul($radix, $base);
  260. }
  261. return $result;
  262. }
  263. //--
  264. // Convert a number back into binary form
  265. //--
  266. function number_to_binary($number, $blocksize)
  267. {
  268. $base = "256";
  269. $result = "";
  270. $div = $number;
  271. while($div > 0)
  272. {
  273. $mod = bcmod($div, $base);
  274. $div = bcdiv($div, $base);
  275. $result = chr($mod) . $result;
  276. }
  277. return str_pad($result, $blocksize, "\x00", STR_PAD_LEFT);
  278. }
  279. //
  280. //Convert hexadecimal format data into binary
  281. //
  282. function hex2bins($data) {
  283. $len = strlen($data);
  284. $newdata='';
  285. for($i=0;$i<$len;$i+=2) {
  286. $newdata .= pack("C",hexdec(substr($data,$i,2)));
  287. }
  288. return $newdata;
  289. }
  290. //function getPrivate($file, $passphrase){
  291. // $p = openssl_pkey_get_private(file_get_contents($file), $passphrase);
  292. // $res = openssl_pkey_get_details($p);
  293. // var_dump($res);
  294. // openssl_free_key($p);
  295. // return array(
  296. // 'n' => bin2hex($res['rsa']['n']),//#模数
  297. // 'e' => bin2hex($res['rsa']['e']),//#公钥指数
  298. // 'd' => bin2hex($res['rsa']['d']),//#私钥指数
  299. // );
  300. //}
  301. //
  302. // Function to load privateKey and publicKey objects from pkcs12 file
  303. // @param $file // $file is expected to be pkcs12 file realpath
  304. // @param $passphrase // $passphrase is expected to be password of privatekey
  305. // @param $keyType // $keyType is expected to be a option of key style (pub/pri)
  306. // @author simonyi peng
  307. //
  308. function getKey($file, $passphrase, $keyType){
  309. $p12_File_Name = ($file);
  310. $certs = array();
  311. $pkcs12 = file_get_contents($p12_File_Name);
  312. openssl_pkcs12_read($pkcs12, $certs, $passphrase);
  313. #var_dump($certs);//可通过var_dump函数查看输出数组的key
  314. $pubKey = $certs['cert'];//公钥数据
  315. $priKey = $certs['pkey'];//私钥数据
  316. if($keyType == 'pubKey'){
  317. return $pubKey;
  318. }
  319. if($keyType == 'priKey'){
  320. return $priKey;
  321. }
  322. }
  323. //
  324. // Function to sign resource String to BASE64
  325. // @author simonyi peng
  326. //
  327. function signByPriKey($resource, $file, $passphrase){
  328. $priKey = getKey($file, $passphrase, 'priKey');
  329. $res = openssl_pkey_get_private($priKey);
  330. if(openssl_sign($resource, $out, $res)){
  331. //var_dump(base64_encode($out));
  332. return base64_encode($out);
  333. }else{
  334. return "";
  335. }
  336. }
  337. //
  338. // Function to verify if signmsg is signed by appointed certificate and resource String
  339. // @param $sign -- sign parameter must be a BASE64 encoding string
  340. // @return 1 if verify victory 0 if verify missed
  341. // @author simonyi peng
  342. //
  343. function verifyByPubKey($resource, $sign, $file, $passphrase){
  344. $pubKey = getKey($file, $passphrase, 'pubKey');
  345. $out = base64_decode($sign);
  346. $res = openssl_pkey_get_public($pubKey);
  347. if(openssl_verify($resource, $out, $res) == 1)
  348. return 1; //echo "verify_success";
  349. else
  350. return 0; //echo "verify_failed";
  351. }
  352. ?>