加密PHP中的时间戳并用Java解密

时间:2016-06-01 04:05:23

标签: java php encryption encoding integer

我需要加密一个unix时间戳来调用第三方API。在他们的文档中,他们指定我必须使用:

  • 算法:128位AES,模式:CBC
  • 填充:PKCS5Padding
  • 初始化向量:" 0000000000000000"

然后他们举了一个例子:

  

客户端必须使用时间戳1464284796测试其实现,初始化向量:&00; 0000000000000000'和密钥b35901b480ca658c8be4341eefe21a80导致base64 auth_token 6BH3hg1cqQJOK6sG8gw7Xw ==

他们甚至提供示例代码来生成加密时间戳,问题是他们使用Java并且我们正在使用PHP。我在PHP中尝试的一切都与预期的输出不匹配,即6BH3hg1cqQJOK6sG8gw7Xw ==。

这是他们的Java样本:

import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import com.google.common.primitives.Longs;
class Encryptor {
    private String initialVector;
    private static final String TRANFORMATION = "AES/CBC/PKCS5Padding";
    private static final String ALGORITHM = "AES";
    String encrypt(SecretKeySpec key, long timestamp) throws Exception {
        byte[] encryptedBytes =
            getEncryptingCipher(key).doFinal(Longs.toByteArray(timestamp));
        return Base64.encodeBase64String(encryptedBytes);
    }
    private Cipher getEncryptingCipher(SecretKeySpec key) throws
    NoSuchAlgorithmException, NoSuchPaddingException, UnsupportedEncodingException,
    InvalidKeyException,
    InvalidAlgorithmParameterException {
        Cipher encryptingCipher = Cipher.getInstance(TRANFORMATION);
        encryptingCipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(initialVector.getBytes()));
        return encryptingCipher;
    }
    private SecretKeySpec getSecretKeySpec(String key) throws DecoderException {
        byte[] keyBytes = Hex.decodeHex(key.toCharArray());
        return new SecretKeySpec(keyBytes, ALGORITHM);
    }
    void setInitialVector(String initialVector) {
        this.initialVector = initialVector;
    }
}

用法:

Encryptor encryptor = new Encryptor();
encryptor.setInitialVector("0000000000000000");
//Expensive operation so only performed once, re-use the key spec instance
SecretKeySpec keySpec =
encryptor.getSecretKeySpec("b35901b480ca658c8be4341eefe21a80");
long timestamp = System.currentTimeMillis() / 1000;
String authToken = encryptor.encrypt(keySpec, timestamp);

我在PHP中的尝试:

[acool@acool ~]$ php -a
Interactive shell

php > echo openssl_encrypt( '1464284796','AES-128-CBC','b35901b480ca658c8be4341eefe21a80',null,'0000000000000000');
8PM7LQM7Xmb2NCBE3Hp00g==
php >

然后:

<?php

function encrypt($message, $initialVector, $secretKey) {
    return base64_encode(
        mcrypt_encrypt(
            MCRYPT_RIJNDAEL_128,
            $secretKey,
            $message,
            MCRYPT_MODE_CBC,
            $initialVector
        )
    );
}

function encrypt_something($input)
{
    $size = mcrypt_get_block_size('rijndael-128', 'cbc');
    $input = pkcs5_pad($input, $size);

    $key = 'b35901b480ca658c8be4341eefe21a80';
    $td = mcrypt_module_open('rijndael-128', '', 'cbc', '');
    $iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
    mcrypt_generic_init($td, $key, '0000000000000000');
    $data = mcrypt_generic($td, $input);
    mcrypt_generic_deinit($td);
    mcrypt_module_close($td);
    $data = base64_encode($data);
    return $data;
}

function pkcs5_pad ($text, $blocksize)
{
    $pad = $blocksize - (strlen($text) % $blocksize);
    return $text . str_repeat(chr($pad), $pad);
}

function pkcs5_unpad($text)
{
    $pad = ord($text{strlen($text)-1});
    if ($pad > strlen($text)) return false;
    if (strspn($text, chr($pad), strlen($text) - $pad) != $pad) return false;
    return substr($text, 0, -1 * $pad);
}

echo encrypt_something('1464284796');

//echo encrypt('1464284796','0000000000000000','b35901b480ca658c8be4341eefe21a80');



[acool@acool ~]$ php -f api.php 
UXRvTOIPiiYfBUoDFRaC5w==

老实说,我不太了解我在做什么,特别是在JAVA中。我甚至不知道如何运行示例代码。

更新

第三方通过修改样本与我们联系:

  

注意:客户端必须使用时间戳测试其实现   1464284796,初始化向量:&#39; 0000000000000000&#39;和   秘密密钥b35901b480ca658c8be4341eefe21a80导致了   base64 auth_token ZnNmKbcdxRrYTDBgQKI9aQ ==

解决方案是改造pack功能:

$ts  = '1464284796';
$key = "b35901b480ca658c8be4341eefe21a80";
$authToken = openssl_encrypt( $ts, 'AES-128-CBC', hextobin($key), null, '0000000000000000');

function hextobin($hexstr) 
{
    $n    = strlen($hexstr); 
    $sbin ="";
    $i    =0; 
    while($i<$n)
    {
        $a =substr($hexstr,$i,2);
        $c = pack("H*",$a); 
        if ($i==0){$sbin=$c;} 
        else {$sbin.=$c;} 
        $i+=2; 
    }
    return $sbin;
} 

2 个答案:

答案 0 :(得分:1)

数字不仅仅是十进制字符串。您必须将它们编码为二进制表示。这可以使用pack完成。由于Longs.toByteArray(timestamp)以64位big-endian表示法对时间戳进行编码,因此您必须匹配此编码:

$ts = "\0\0\0\0" . pack('N', '1464284796');
echo openssl_encrypt( $ts, 'AES-128-CBC', hex2bin('b35901b480ca658c8be4341eefe21a80'), 
        null, '0000000000000000'));

答案 1 :(得分:0)

查看您正在使用的Java API。他们真的期待十六进制字符串吗这在Java中非常罕见。

你的代码在这里写着“initialVector.getBytes()”,这表明API需要一个字节数组,而不是十六进制字符串的破碎字节数组表示:)

PHP或JS代码经常使用十六进制字符串的原因是这些语言中不支持字节数组(至少在传统上,现代JS中实际上存在字节数组)。