Subversion Repositories php_utils

Rev

Rev 58 | Rev 60 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. <?php
  2.  
  3. /*
  4.  * ViaThinkSoft Modular Crypt Format 1.0
  5.  * Revision 2023-02-26
  6.  * Copyright 2023 Daniel Marschall, ViaThinkSoft
  7.  *
  8.  * Licensed under the Apache License, Version 2.0 (the "License");
  9.  * you may not use this file except in compliance with the License.
  10.  * You may obtain a copy of the License at
  11.  *
  12.  *     http://www.apache.org/licenses/LICENSE-2.0
  13.  *
  14.  * Unless required by applicable law or agreed to in writing, software
  15.  * distributed under the License is distributed on an "AS IS" BASIS,
  16.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17.  * See the License for the specific language governing permissions and
  18.  * limitations under the License.
  19.  */
  20.  
  21. /*
  22.  
  23. ViaThinkSoft Modular Crypt Format 1.0 performs a simple hash or HMAC operation.
  24. No key derivation function or iterations are performed.
  25.  
  26. Format:
  27.         $1.3.6.1.4.1.37476.3.0.1.1$a=<algo>,m=<mode>$<salt>$<hash>
  28.  
  29. where <algo> is any valid hash algorithm (name scheme of PHP hash_algos() preferred), e.g.
  30.         sha3-512
  31.         sha3-384
  32.         sha3-256
  33.         sha3-224
  34.         sha512
  35.         sha512/256
  36.         sha512/224
  37.         sha384
  38.         sha256
  39.         sha224
  40.         sha1
  41.         md5
  42.  
  43. Valid <mode> :
  44.         sp = salt + password
  45.         ps = password + salt
  46.         sps = salt + password + salt
  47.         hmac = HMAC (salt is the key)
  48.  
  49. Link to the online specification:
  50.         https://oidplus.viathinksoft.com/oidplus/?goto=oid%3A1.3.6.1.4.1.37476.3.0.1.1
  51.  
  52. Reference implementation in PHP:
  53.         https://github.com/danielmarschall/php_utils/blob/master/vts_crypt.inc.php
  54.  
  55. */
  56.  
  57. define('OID_MCF_VTS_V1', '1.3.6.1.4.1.37476.3.0.1.1'); // { iso(1) identified-organization(3) dod(6) internet(1) private(4) enterprise(1) 37476 specifications(3) misc(0) modular-crypt-format(1) vts-crypt-v1(1) }
  58.  
  59. define('BASE64_RFC4648_ALPHABET', '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/=');
  60. define('BASE64_CRYPT_ALPHABET',   './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz ');
  61.  
  62. function crypt_radix64_encode($str) {
  63.         $x = $str;
  64.         $x = base64_encode($x);
  65.         $x = trim(strtr($x, BASE64_RFC4648_ALPHABET, BASE64_CRYPT_ALPHABET));
  66.         return $x;
  67. }
  68.  
  69. function crypt_radix64_decode($str) {
  70.         $x = $str;
  71.         $x = trim(strtr($x, BASE64_CRYPT_ALPHABET, BASE64_RFC4648_ALPHABET));
  72.         $x = base64_decode($x);
  73.         return $x;
  74. }
  75.  
  76. assert(crypt_radix64_decode(crypt_radix64_encode('hallo')));
  77.  
  78. function crypt_modular_format($id, $bin_salt, $bin_hash, $params=null) {
  79.         // $<id>[$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]]
  80.         $out = '$'.$id;
  81.         if (!is_null($params)) {
  82.                 $ary_params = array();
  83.                 //ksort($params);
  84.                 foreach ($params as $name => $value) {
  85.                         $ary_params[] = "$name=$value";
  86.                 }
  87.                 $out .= '$'.implode(',',$ary_params);
  88.         }
  89.         $out .= '$'.crypt_radix64_encode($bin_salt);
  90.         $out .= '$'.crypt_radix64_encode($bin_hash);
  91.         return $out;
  92. }
  93.  
  94. function crypt_modular_format_decode($mcf) {
  95.         $ary = explode('$', $mcf);
  96.  
  97.         $dummy = array_shift($ary);
  98.         if ($dummy !== '') return false;
  99.  
  100.         $dummy = array_shift($ary);
  101.         $id = $dummy;
  102.  
  103.         $params = array();
  104.         $dummy = array_shift($ary);
  105.         if (strpos($dummy, '=') !== false) {
  106.                 $params_ary = explode(',',$dummy);
  107.                 foreach ($params_ary as $param) {
  108.                         $bry = explode('=', $param, 2);
  109.                         if (count($bry) > 1) {
  110.                                 $params[$bry[0]] = $bry[1];
  111.                         }
  112.                 }
  113.         } else {
  114.                 array_unshift($ary, $dummy);
  115.         }
  116.  
  117.         if (count($ary) > 1) {
  118.                 $dummy = array_shift($ary);
  119.                 $bin_salt = crypt_radix64_decode($dummy);
  120.         } else {
  121.                 $bin_salt = '';
  122.         }
  123.  
  124.         $dummy = array_shift($ary);
  125.         $bin_hash = crypt_radix64_decode($dummy);
  126.  
  127.         return array('id' => $id, 'salt' => $bin_salt, 'hash' => $bin_hash, 'params' => $params);
  128. }
  129.  
  130. function vts_crypt($algo, $str_password, $str_salt, $ver='1', $mode='ps') {
  131.         if ($ver == '1') {
  132.                 if ($mode == 'sp') {
  133.                         $payload = $str_salt.$str_password;
  134.                         if (($algo === 'sha3-512') && !in_array($algo, hash_algos()) && function_exists('sha3_512')) {
  135.                                 $bin_hash = sha3_512($payload, true);
  136.                         } else {
  137.                                 $bin_hash = hash($algo, $payload, true);
  138.                         }
  139.                 } else if ($mode == 'ps') {
  140.                         $payload = $str_password.$str_salt;
  141.                         if (($algo === 'sha3-512') && !in_array($algo, hash_algos()) && function_exists('sha3_512')) {
  142.                                 $bin_hash = sha3_512($payload, true);
  143.                         } else {
  144.                                 $bin_hash = hash($algo, $payload, true);
  145.                         }
  146.                 } else if ($mode == 'sps') {
  147.                         $payload = $str_salt.$str_password.$str_salt;
  148.                         if (($algo === 'sha3-512') && !in_array($algo, hash_algos()) && function_exists('sha3_512')) {
  149.                                 $bin_hash = sha3_512($payload, true);
  150.                         } else {
  151.                                 $bin_hash = hash($algo, $payload, true);
  152.                         }
  153.                 } else if ($mode == 'hmac') {
  154.                         // Note: Actually, we should use hash_hmac_algos(), but this requires PHP 7.2, and we would like to stay compatible with PHP 7.0 for now
  155.                         if (($algo === 'sha3-512') && !in_array($algo, hash_algos()) && function_exists('sha3_512_hmac')) {
  156.                                 $bin_hash = sha3_512_hmac($str_password, $str_salt, true);
  157.                         } else {
  158.                                 $bin_hash = hash_hmac($algo, $str_password, $str_salt, true);
  159.                         }
  160.                 } else {
  161.                         throw new Exception("Invalid VTS crypt version 1 mode. Expect sp, ps, sps, or hmac.");
  162.                 }
  163.                 $bin_salt = $str_salt;
  164.                 return crypt_modular_format(OID_MCF_VTS_V1, $bin_salt, $bin_hash, array('a'=>$algo,'m'=>$mode));
  165.         } else {
  166.                 throw new Exception("Invalid VTS crypt version, expect 1.");
  167.         }
  168. }
  169.  
  170. function vts_crypt_convert_from_old_oidplus($authkey, $salt) {
  171.         if (preg_match('@^A1([abcd])#(.+):(.+)$@', $authkey, $m)) {
  172.                 // A1a#hashalgo:X with X being H(salt+password) in hex- or rfc4648-base64-notation
  173.                 // A1b#hashalgo:X with X being H(password+salt) in hex- or rfc4648-base64-notation
  174.                 // A1c#hashalgo:X with X being H(salt+password+salt) in hex- or rfc4648-base64-notation
  175.                 // A1d#hashalgo:X with X being H_HMAC(password,salt) in hex- or rfc4648-base64-notation
  176.                 $mode = ''; // avoid PHPstan warning
  177.                 if ($m[1] == 'a') $mode = 'sp';
  178.                 else if ($m[1] == 'b') $mode = 'ps';
  179.                 else if ($m[1] == 'c') $mode = 'sps';
  180.                 else if ($m[1] == 'd') $mode = 'hmac';
  181.                 else assert(false);
  182.                 $algo = $m[2];
  183.                 $bin_salt = $salt;
  184.                 if (($algo == 'sha3-512') || ($algo == 'sha3-384') || ($algo == 'sha512') || ($algo == 'sha384')) {
  185.                         $bin_hash = base64_decode($m[3]);
  186.                 } else {
  187.                         $bin_hash = hex2bin($m[3]);
  188.                 }
  189.                 return crypt_modular_format(OID_MCF_VTS_V1, $bin_salt, $bin_hash, array('a'=>$algo,'m'=>$mode));
  190.         } else if (preg_match('@^A2#(.+)$@', $authkey, $m)) {
  191.                 // A2#X with X being sha3(salt+password) in rfc4648-base64-notation
  192.                 $mode = 'sp';
  193.                 $algo = 'sha3-512';
  194.                 $bin_salt = $salt;
  195.                 $bin_hash = base64_decode($m[1]);
  196.                 return crypt_modular_format(OID_MCF_VTS_V1, $bin_salt, $bin_hash, array('a'=>$algo,'m'=>$mode));
  197.         } else if (preg_match('@^A3#(.+)$@', $authkey, $m)) {
  198.                 // A3#X with X being bcrypt  [not VTS hash!]
  199.                 return $m[1];
  200.         } else {
  201.                 // Nothing to convert
  202.                 return $authkey;
  203.         }
  204. }
  205.