微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

充气城堡:在同一时间范围内~ 1 秒多个 PGPS 签名相同

如何解决充气城堡:在同一时间范围内~ 1 秒多个 PGPS 签名相同

在循环中测试 PGPSsignature 的生成时,始终使用相同的输入,我注意到我在很短的时间内获得了相同的签名。

这确实让我感到惊讶:我一直期待签名是不可复制的。

这是有意为之吗?

大约 1 秒后返回不同的签名。

使用的充气城堡包:
bcpg-jdk15on-168.jar
bcprov-jdk15on-168.jar

Java 版本:
openJDK v14.0.2,x64

这里有一个独立的小例子 Proggy 来强调这一点:

import java.io.IOException;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.time.zoneddatetime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Base64;
import java.util.Date;
import java.util.Iterator;

import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPair;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;

public class PgpSimpleSigner {

    private static final DateTimeFormatter FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS" + "SSS" + "SSS");

    public         final PGPPrivateKey     privateKey;
    public         final PGPPublicKey      publicKey;

    private PgpSimpleSigner() throws IOException,PGPException {

        final RSAKeyGenerationParameters kgp = new RSAKeyGenerationParameters(BigInteger.valueOf(0x10001),new SecureRandom(),2048,12);
        final RSAKeyPairGenerator        kpg = new RSAKeyPairGenerator();
                                         kpg.init(kgp);

        final PGPKeyPair  keyPair = new BcPGPKeyPair(PGPPublicKey.RSA_SIGN,kpg.generateKeyPair(),new Date());

        this.privateKey = keyPair.getPrivateKey();
        this.publicKey  = keyPair.getPublicKey();
    }

    private PGPSignature sign(final String signMeString) throws Exception {

        final int keyAlgorithm  = PublicKeyAlgorithmTags.RSA_SIGN;
        final int hashAlgorithm = PGPUtil.SHA256;

        final JcaPGPContentSignerBuilder csb = new JcaPGPContentSignerBuilder(keyAlgorithm,hashAlgorithm);
                                         csb.setProvider(new BouncyCastleProvider());

        final PGPSignatureGenerator          sGen  = new PGPSignatureGenerator(csb);
        final PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
        /*
         * (spGen contains NO Subpackets,in particular no SignatureCreationTime)
         */

        sGen.init(PGPSignature.CANONICAL_TEXT_DOCUMENT,this.privateKey);

        this.publicKey.getUserIDs().forEachRemaining(userID -> {
            /*
             * Our Test PublicKey has no Users associated,so this Loop is not entered!
             */
            spGen.addSignerUserID(false,userID);
            /*
             * Suspicion: the Example code for this logic in
             * org.bouncycastle.openpgp.examples.ClearSignedFileProcessor
             * is incorrect? Maybe following should be outside the loop?...
             */
            sGen .setHashedSubpackets(spGen.generate()); // never executed!
        });

        sGen.update(signMeString.getBytes());

        return sGen.generate();
        /*
         * The above logic based on Method
         * signFile(String,InputStream,OutputStream,char[],String)
         * in
         * org.bouncycastle.openpgp.examples.ClearSignedFileProcessor.
         * 
         * ...but without the complicated CR/LF & Whitespace logic
         * as we kNow our input String is RFC 4880 compliant.
         */
    }

    public static void main(final String[] args) throws Throwable {

        final PgpSimpleSigner pgpSimpleSigner = new PgpSimpleSigner();

        byte[] bcSigBytesPrev = {};
        long   t0             = System.nanoTime();

        while (true) {
            final long         nsSinceDelta = System.nanoTime() - t0;
            final PGPSignature bcSig        = pgpSimpleSigner.sign("Sign me,I'm RFC 4880 compliant");
            final byte[]       bcSigBytes   = bcSig.getSignature();

            if (Arrays.compare(bcSigBytesPrev,bcSigBytes) != 0) {
                               bcSigBytesPrev = bcSigBytes;
                
                System.out.println(FMT.format(zoneddatetime.Now()) + "\t" + nsSinceDelta + "\t" + Base64.getEncoder().encodetoString(bcSigBytes));

                t0 = System.nanoTime();
            }
        }
    }
}

解决方法

查看org.bouncycastle.bcpg.sig.SignatureCreationTime.timeToBytes()的源代码,了解如何将日期值添加到字节数组中进行签名:

protected static byte[] timeToBytes(
    Date    date)
{
    byte[]    data = new byte[4];
    long        t = date.getTime() / 1000;
    
    data[0] = (byte)(t >> 24);
    data[1] = (byte)(t >> 16);
    data[2] = (byte)(t >> 8);
    data[3] = (byte)t;
    
    return data;
}

如您所见,使用了精确到秒的当前时间(date.getTime() 返回毫秒,/ 1000“删除”毫秒部分)。

看起来这符合 RFC 4880 - 5.9. Literal Data Packet (Tag 11) 部分:

 - A four-octet number that indicates a date associated with the
   literal data.  Commonly,the date might be the modification date
   of a file,or the time the packet was created,or a zero that
   indicates no specific time.

所以它看起来像指定的那样工作。

,

默认情况下,PGPSignatureSubpacketGenerator 不包含子数据包。

Creation Time 是 Must 子包,如果在 PGPSignatureSubpacketGenerator 中没有找到,将在 PGPSignatureGenerator#generate() 使用 "Now" 秒。
(正如@Progman 正确指出的那样)

正如我在原始问题的代码中推测的那样,示例代码在 org.bouncycastle.openpgp.examples.ClearSignedFileProcessor 不完美。
现在已修复。见BC Issue #965

我扩展了我的示例代码以添加一个包含纳秒的自定义子数据包, 这会导致每次生成不同的签名:

import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.SecureRandom;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Base64;
import java.util.Date;

import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
import org.bouncycastle.bcpg.SignatureSubpacket;
import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPair;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;

public class PgpSimpleSigner {

    private static final DateTimeFormatter FMT          = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS" + "SSS" + "SSS");

    private static final int               CUSTOM_100   = 100;   // (According to RFC 4880,available for use)
    private static final boolean           NON_CRITICAL = false;

    public         final PGPPrivateKey     privateKey;
    public         final PGPPublicKey      publicKey;

    private PgpSimpleSigner() throws IOException,PGPException {

        final RSAKeyGenerationParameters kgp = new RSAKeyGenerationParameters(BigInteger.valueOf(0x10001),new SecureRandom(),2048,12);
        final RSAKeyPairGenerator        kpg = new RSAKeyPairGenerator();
        ;                                kpg.init(kgp);

        final PGPKeyPair  keyPair = new BcPGPKeyPair(PGPPublicKey.RSA_SIGN,kpg.generateKeyPair(),new Date());

        this.privateKey = keyPair.getPrivateKey();
        this.publicKey  = keyPair.getPublicKey();
    }

    private PGPSignature sign(final String signMeString) throws Exception {

        final int keyAlgorithm  = PublicKeyAlgorithmTags.RSA_SIGN;
        final int hashAlgorithm = PGPUtil.SHA256;

        final JcaPGPContentSignerBuilder     csb = new JcaPGPContentSignerBuilder(keyAlgorithm,hashAlgorithm);
        ;                                    csb.setProvider(new BouncyCastleProvider());

        final PGPSignatureGenerator          sGen  = new PGPSignatureGenerator(csb);
        final PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();

        setCreationTimeWithCustomNanos      (spGen);

        sGen.init(PGPSignature.CANONICAL_TEXT_DOCUMENT,this.privateKey);

        this.publicKey.getUserIDs().forEachRemaining(userID -> {
            /*
             * (our Test PublicKey has no Users associated,so this Loop is not entered)
             */
            spGen.addSignerUserID(false,userID);
        });
        sGen.setHashedSubpackets(spGen.generate());

        sGen.update(signMeString.getBytes());

        return sGen.generate();
        /*
         * The above logic based on Method
         * signFile(String,InputStream,OutputStream,char[],String)
         * in
         * org.bouncycastle.openpgp.examples.ClearSignedFileProcessor.
         * 
         * ...but without the complicated CR/LF & Whitespace logic
         * as we know our input String is RFC 4880 compliant.
         */
    }

    /**
     * Bouncy Castle defaults to use Seconds since the Epoch to set Creation Time.<br>
     * (this is done in {@link PGPSignatureGenerator#generate()}).<br>
     * <br>
     * We set the Creation Time explicitly to {@code "now"}...<br>
     * ...and use the Nanoseconds from {@code "now"} to create a Custom Subpacket.<br>
     * <br>
     * Given the elapsed time necessary to calculate a Signature on contemporary Hardware,* this effectively adds a Salt to the deterministic RSA Algorithm,* thus making the Signature unique.
     * 
     * @param spGen
     */
    private static void setCreationTimeWithCustomNanos(final PGPSignatureSubpacketGenerator spGen) {

        final Instant nowInstant    = Instant.now();
        final Date    nowTime       = Date.from(nowInstant);

        final int     nowNanos      = nowInstant.getNano();
        final byte[]  nowNanosBytes = new byte[Integer.BYTES];

        ByteBuffer.wrap(nowNanosBytes).putInt(nowNanos);

        spGen.setSignatureCreationTime(                             NON_CRITICAL,nowTime);
        spGen.addCustomSubpacket(new SignatureSubpacket(CUSTOM_100,NON_CRITICAL,false,nowNanosBytes) {});
    }

    public static void main(final String[] args) throws Throwable {

        final PgpSimpleSigner pgpSimpleSigner = new PgpSimpleSigner();

        byte[] bcSigBytesPrev = {};
        long   t0             = System.nanoTime();

        while (true) {
            final long         nsSinceDelta = System.nanoTime() - t0;
            final PGPSignature bcSig        = pgpSimpleSigner.sign("Sign me,I'm RFC 4880 compliant");
            final byte[]       bcSigBytes   = bcSig.getSignature();

            if (Arrays.compare(bcSigBytesPrev,bcSigBytes) != 0) {
                ;              bcSigBytesPrev = bcSigBytes;

                System.out.println(FMT.format(ZonedDateTime.now()) + "\t" + nsSinceDelta + "\t" + Base64.getEncoder().encodeToString(bcSigBytes));

                t0 = System.nanoTime();
            }
        }
    }
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?