Java 7:使用密码"RSA/ECB/OAEPWithSHA1AndMGF1Padding"使用公共RSA密钥加密的输出与openssl命令不匹配



我们必须使用需要随机生成盐的HMAC-SHA256加密我们的数据。我们是这样生成盐的:

 public String generateSalt() throws Exception
    {
        KeyGenerator keyGen;
        String salt = null;
        try
        {
            keyGen = KeyGenerator.getInstance( "HmacSHA256" );
            keyGen.init( 128 );
            SecretKey key = keyGen.generateKey();
            byte[] encodedKey = key.getEncoded();
            salt = Base64.encodeBase64String( key.getEncoded() );
            LOG.info( "Salt : " + salt );
        }
        catch ( NoSuchAlgorithmException  )
        {
            e.printStackTrace();
            throw e;
        }
        return salt;
    }

根据我们的测试,这个产盐部分是正确的。我对下一部分有问题:

现在我必须将此salt以二进制格式写入文件(例如命名为pie_raw),这是在

    private void writeToFile( byte[] saltBytes, String fileName ) throws FileNotFoundException, IOException
    {
        DataOutputStream out = new DataOutputStream( new FileOutputStream( enviro.getOutputFilePath()
                + fileName ) );
        out.write( saltBytes );
        out.close();
        LOG.info( " Raw file created : " + enviro.getOutputFilePath() + fileName );
    }

然后,我必须用提供的RSA公钥加密这个盐。对于Java实现,密码将是"RSA/ECB/OAEPWithSHA1AndMGF1Padding"。最后将二进制密文写入名为"pie_key"的文件中。这部分是这样实现的:

    private byte[] encryptSalt( String salt ) throws Exception
    {
        byte[] cipheredKey = null;
        try
        {
             String keyString= readKeyFile( enviro.getPublicKeyFile() );
             byte[] pem = pemToDer(keyString); 
             PublicKey publicKey = derToPublicKey(pem);

            //PublicKey publicKey = getPublicKey( enviro.getPublicKeyFile() );
            // Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
            Cipher rsaCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding");
            rsaCipher.init( Cipher.ENCRYPT_MODE, publicKey );
            cipheredKey = rsaCipher.doFinal( salt.getBytes( "UTF-8" ) );//"UTF-8"
            LOG.info( "Cyphered key : " + cipheredKey.toString() );
        }
        catch ( IOException | GeneralSecurityException e )
        {
            e.printStackTrace();
            throw e;
        }
        return cipheredKey;
    }
    static String readKeyFile( String path )
            throws IOException
    {
        String line = null;
        try (BufferedReader br =
                new BufferedReader( new FileReader( path ) ))
        {
            StringBuilder sb = new StringBuilder();
            line = br.readLine();
            while ( line != null )
            {
                sb.append( line );
                sb.append( "n" );
                line = br.readLine();
            }
            return sb.toString();
        }
    }
    public static byte[] pemToDer( String pemKey ) throws GeneralSecurityException
    {
        String[] parts = pemKey.split( "-----" );
        return DatatypeConverter.parseBase64Binary( parts[ parts.length / 2 ] );
    }
    public static PublicKey derToPublicKey( byte[] asn1key ) throws GeneralSecurityException
    {
        X509EncodedKeySpec spec = new X509EncodedKeySpec( asn1key );
        KeyFactory keyFactory = KeyFactory.getInstance( "RSA" );
        return keyFactory.generatePublic( spec );
    }

通过调用上面的"writeToFile"方法,将这个加密的salt以二进制格式写入名为"pie_key"的文件中。

现在文件"pie_key"的内容应该与cmd的输出相匹配:

openssl rsautl -encrypt -pubin -inkey wrap_pie_key_rsa.pem -oaep -in pie_key.raw -out pie_key

但是无论我尝试什么(你可能会发现一些方法的迹象,我尝试过)都没有工作,这意味着最终的二进制加密盐与openssl cmd的输出不匹配。

知道我做错了什么吗?

我正在使用Java 7。pem(部分)看起来像

-----BEGIN PUBLIC KEY-----
MIIBIjANBgk345iG9w0BAQEFAA54328AMIIBCgKCAQEAt4GLJGPmvYdxwwAe59n3
. 
.
.
.
7QIDNQAB
-----END PUBLIC KEY-----

首先,正如Artjom已经提到的,OAEP或pkcs# 1 v1.5兼容的填充是随机的。因此,即使对相同的盐进行多次加密,也不会得到相同的值。您只能对结果进行解密,以查看加密是否成功。


此外,您说您需要二进制盐,但是您首先将盐编码为base64。您的加密不太可能包含已编码的盐。也许您需要对加密的输出进行编码,而不是对盐进行编码。

错误编码发生在下一行:

salt = Base64.encodeBase64String( key.getEncoded() );

最后,尽管新的HMAC密钥通常由完全随机的字节组成,但我想说这不是生成盐的正确方法。只需使用:

SecureRandom rngForSalt = new SecureRandom();
byte[] salt = new byte[SALT_SIZE];
rngForSalt.nextBytes(salt);

还请注意,Bouncy Castle轻量级API(即直接调用org.bouncycastle功能)包含一个PEM编解码器。不需要自己编程或破解。


试试这个Java 8代码。需要弹性城堡提供程序类(不需要注册提供程序,这只是用于PEM处理)。

package nl.maartenbodewes.stackoverflow;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.bouncycastle.util.io.pem.PemWriter;
public class GenerateAndWrapHMACKey {
    public static SecretKey generateHMACKey() throws Exception {
        final KeyGenerator keyGen;
        try {
            keyGen = KeyGenerator.getInstance("HmacSHA256");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("HMAC KeyGeneration should be available");
        }
        keyGen.init(128);
        SecretKey key = keyGen.generateKey();
        return key;
    }
    public static void writeToFile(SecretKey key, String filename)
            throws IOException {
        // file handling probably should be in a separate class
        Files.write((new File(filename)).toPath(), key.getEncoded());
    }
    public static RSAPublicKey readRSAPublicKey(String filename) throws IOException, InvalidKeySpecException {
        try (PemReader reader = new PemReader(new FileReader(filename))) {
            PemObject pemObject = reader.readPemObject();
            KeyFactory kf;
            try {
                kf = KeyFactory.getInstance("RSA");
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException("RSA key factory not available", e);
            }
            KeySpec keySpec = new X509EncodedKeySpec(pemObject.getContent());
            try {
                return (RSAPublicKey) kf.generatePublic(keySpec);
            } catch (ClassCastException e) {
                throw new InvalidKeySpecException("That's no RSA key", e);
            }
        }
    }
    public static byte[] wrapKey(Key key, RSAPublicKey wrappingKey) throws InvalidKeyException, IllegalBlockSizeException {
        Cipher rsaWrapper;
        try {
            rsaWrapper = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding");
            rsaWrapper.init(Cipher.WRAP_MODE, wrappingKey);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
            throw new RuntimeException("RSA OAEP should be available for RSA public key", e);
        }
        return rsaWrapper.wrap(key);
    }
    public static void main(String[] args) throws Exception {
        // we need an RSA PEM key first I guess :)
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
        kpg.initialize(1024, new SecureRandom());
        KeyPair kp = kpg.generateKeyPair();
        String publicKeyFilename = "rsa_pub.pem";
        try (PemWriter pemWriter = new PemWriter(new FileWriter(publicKeyFilename))) {
            pemWriter.writeObject(new PemObject("PUBLIC KEY", kp.getPublic().getEncoded()));
        }
        RSAPublicKey wrappingRSAPublicKey = readRSAPublicKey(publicKeyFilename);
        SecretKey hmacKey = generateHMACKey();
        byte[] wrappedKey = wrapKey(hmacKey, wrappingRSAPublicKey);
        System.out.println(Base64.getEncoder().encodeToString(wrappedKey));
    }
}

最新更新