This post was updated 525 days ago and some of the ideas may be out of date.

PHP版

/**
 *
 * 可逆随机加密函数
 * @param string $string 明文或密文
 * @param bool $operation 是否加密。true表示加密,false表示解密。
 * @param string $key 密钥
 * @param int $outTime 密文有效期,单位为秒
 * @param string $encryptType 加密方式,有md5和sha1两种。加密解密需要统一使用同一种方式才能正确还原明文。
 * @return string
 */
function encryption(string $string, bool $operation = true, string $key = '', int $outTime = 0, string $encryptType = 'md5'): string
{
    if ($encryptType === 'md5') {
        // 使用md5方式
        $longLen = 2;
        $halfLen = 1;
    } else {
        // 使用sha1方式
        $longLen = 40;
        $halfLen = 20;
        $encryptType = 'sha1';
    }
    $keyLength = 4;
    $fixedKey = hash($encryptType, $key);
    $hashedKey = md5(substr($fixedKey, $halfLen, $halfLen));
    $runToKey = $operation ? substr(hash($encryptType, md5($key . $string)), -$keyLength) : substr($string, 0, $keyLength);
    $keys = hash($encryptType, substr($runToKey, 0, $halfLen) . substr($fixedKey, 0, $halfLen) . substr($runToKey, $halfLen) . substr($fixedKey, $halfLen));
    $string = $operation ? sprintf('%010d', $outTime ? $outTime + time() : 0) . substr(md5($string . $hashedKey), 0, $halfLen) . $string : base64_decode(substr($string, $keyLength));
    $result = '';
    $stringLength = strlen($string);
    for ($i = 0; $i < $stringLength; $i++) {
        $result .= chr(ord($string[$i]) ^ ord($keys[$i % $longLen]));
    }
    if ($operation) {
        return $runToKey . str_replace('=', '', base64_encode($result));
    } else {
        if (((int)substr($result, 0, 10) === 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, $halfLen) === substr(md5(substr($result, $halfLen + 10) . $hashedKey), 0, $halfLen)) {
            return substr($result, $halfLen + 10);
        } else {
            return '';
        }
    }
}

JavaScript版,依赖CryptoJS

/**
 *
 * 可逆随机加密函数
 * @param {string} string 明文或密文
 * @param {boolean} operation 是否加密。true表示加密,false表示解密。
 * @param {string} key 密钥
 * @param {number} outTime 密文有效期,单位为秒
 * @param {string} encryptType 加密方式,有md5和sha1两种。加密解密需要统一使用同一种方式才能正确还原明文。
 * @return {string}
 */
function encryption(string, operation = true, key = '', outTime = 0, encryptType = 'sha1') {
  let longLen, halfLen;
  if (encryptType === 'md5') {
    // 使用md5方式
    longLen = 2;
    halfLen = 1;
  } else {
    // 使用sha1方式
    longLen = 40;
    halfLen = 20;
    encryptType = 'sha1';
  }
  const keyLength = 4;
  const fixedKey = CryptoJS[encryptType.toUpperCase()](key).toString();
  const hashedKey = CryptoJS.MD5(fixedKey.substring(halfLen, halfLen + halfLen)).toString();
  const runToKey = operation ? CryptoJS[encryptType.toUpperCase()](CryptoJS.MD5(key + string).toString()).toString().substr(-keyLength) : string.substring(0, keyLength);
  const keys = CryptoJS[encryptType.toUpperCase()](runToKey.substring(0, halfLen) + fixedKey.substring(0, halfLen) + runToKey.substring(halfLen) + fixedKey.substring(halfLen)).toString();
  string = operation ? String(outTime ? outTime + parseInt(String(Date.now() / 1000)) : 0).padStart(10, '0') + CryptoJS.MD5(string + hashedKey).toString().substr(0, halfLen) + string : atob(string.substring(keyLength));
  let result = '';
  const stringLength = string.length;
  for (let i = 0; i < stringLength; i++) {
    result += String.fromCharCode(string.charCodeAt(i) ^ keys.charCodeAt(i % longLen));
  }
  if (operation) {
    return runToKey + btoa(result).replace(/=/g, '');
  } else {
    if ((parseInt(result.substr(0, 10)) === 0 || parseInt(result.substr(0, 10)) - parseInt(String(Date.now() / 1000)) > 0) && result.substr(10, halfLen) === CryptoJS.MD5(result.substr(halfLen + 10) + hashedKey).toString().substr(0, halfLen)) {
      return result.substr(halfLen + 10);
    } else {
      return '';
    }
  }
}