所有Android版本的安全随机数生成

4

我正在尝试开发一款涉及加密的Android应用程序。计划使用AES-CTR模式和Whirlpool的PBKDF2进行密钥拉伸。

我打算实现一个新的Bouncy Castle(密码工具库)实现,而不是Android内置的旧实现,以确保在任何Android版本上都能按预期运行。

但是我遇到了一些问题,无法找到一种可靠的方法来生成盐和密钥的随机数。我曾经看到有人说Android内置的安全随机数在一些旧的Android版本上存在漏洞,并且我也听说大多数Android手机很难保持/dev/random中高熵和常见的块。这难道对/dev/urandom的安全性没有巨大影响吗?因此,我正在寻找利用手机传感器收集更多熵的好方法。


抱歉,我不确定你的技能水平。我之前实现过一个简化版DES,所以我认为其他人也做过类似的事情...如果你是新手,我建议你现在只使用Java随机数生成器创建你想要的项目。一旦它可以工作,你应该尝试改进(和实验)你的随机数生成器。 - But I'm Not A Wrapper Class
1
@Mohammad 你说传感器昂贵是什么意思? - Hampus Andersson
从内存和功耗的角度来看,使用它是昂贵的。在Android手机应用程序中解析字符串也是昂贵的。这种开销会严重减慢应用程序的速度。另外,这与设计相关,但如果有人的手机传感器损坏了怎么办? - But I'm Not A Wrapper Class
你可以将传感器用作良好 PRNG 的种子。但不要使用传感器收集所有随机信息,而只需将其用作种子。 - Eugene Mayevski 'Callback
@EugeneMayevski'EldoSCorp 是的,一旦你有了一个良好的PRNG种子(我假设SHA1PRNG应该可以),那么大部分问题都解决了。 - Maarten Bodewes
显示剩余2条评论
1个回答

4
以下类应该帮助您缓解Android SecureRandom类的问题。这段代码是为了避免小细节而创建的,而不是文本。
/**
 * A strengthener that can be used to generate and re-seed random number
 * generators that do not seed themselves appropriately.
 * 
 * @author owlstead
 */
public class SecureRandomStrengthener {
    private static final String DEFAULT_PSEUDO_RANDOM_NUMBER_GENERATOR = "SHA1PRNG";

    private static final EntropySource TIME_ENTROPY_SOURCE = new EntropySource() {

        final ByteBuffer timeBuffer = ByteBuffer.allocate(Long.SIZE / Byte.SIZE
                * 2);

        @Override
        public ByteBuffer provideEntropy() {
            this.timeBuffer.clear();
            this.timeBuffer.putLong(System.currentTimeMillis());
            this.timeBuffer.putLong(System.nanoTime());
            this.timeBuffer.flip();
            return this.timeBuffer;
        }
    };

    private final String algorithm;
    private final List<EntropySource> entropySources = new LinkedList<EntropySource>();
    private final MessageDigest digest;
    private final ByteBuffer seedBuffer;

    /**
     * Generates an instance of a {@link SecureRandomStrengthener} that
     * generates and re-seeds instances of {@code "SHA1PRNG"}.
     * 
     * @return the strengthener, never null
     */
    public static SecureRandomStrengthener getInstance() {
        return new SecureRandomStrengthener(
                DEFAULT_PSEUDO_RANDOM_NUMBER_GENERATOR);
    }

    /**
     * Generates an instance of a {@link SecureRandomStrengthener} that
     * generates instances of the given argument. Note that the availability of
     * the given algorithm arguments in not tested until generation.
     * 
     * @param algorithm
     *            the algorithm indicating the {@link SecureRandom} instance to
     *            use
     * @return the strengthener, never null
     */
    public static SecureRandomStrengthener getInstance(final String algorithm) {
        return new SecureRandomStrengthener(algorithm);
    }

    private SecureRandomStrengthener(final String algorithm) {
        if (algorithm == null || algorithm.length() == 0) {
            throw new IllegalArgumentException(
                    "Please provide a PRNG algorithm string such as SHA1PRNG");
        }

        this.algorithm = algorithm;
        try {
            this.digest = MessageDigest.getInstance("SHA1");
        } catch (final NoSuchAlgorithmException e) {
            throw new IllegalStateException(
                    "MessageDigest to create seed not available", e);
        }
        this.seedBuffer = ByteBuffer.allocate(this.digest.getDigestLength());
    }

    /**
     * Add an entropy source, which will be called for each generation and
     * re-seeding of the given random number generator.
     * 
     * @param source
     *            the source of entropy
     */
    public void addEntropySource(final EntropySource source) {
        if (source == null) {
            throw new IllegalArgumentException(
                    "EntropySource should not be null");
        }
        this.entropySources.add(source);
    }

    /**
     * Generates and seeds a random number generator of the configured
     * algorithm. Calls the {@link EntropySource#provideEntropy()} method of all
     * added sources of entropy.
     * 
     * @return the random number generator
     */
    public SecureRandom generateAndSeedRandomNumberGenerator() {
        final SecureRandom secureRandom;
        try {
            secureRandom = SecureRandom.getInstance(this.algorithm);
        } catch (final NoSuchAlgorithmException e) {
            throw new IllegalStateException("PRNG is not available", e);
        }

        reseed(secureRandom);
        return secureRandom;
    }

    /**
     * Re-seeds the random number generator. Calls the
     * {@link EntropySource#provideEntropy()} method of all added sources of
     * entropy.
     * 
     * @param secureRandom
     *            the random number generator to re-seed
     */
    public void reseed(final SecureRandom secureRandom) {
        this.seedBuffer.clear();
        secureRandom.nextBytes(this.seedBuffer.array());

        for (final EntropySource source : this.entropySources) {
            final ByteBuffer entropy = source.provideEntropy();
            if (entropy == null) {
                continue;
            }

            final ByteBuffer wipeBuffer = entropy.duplicate();
            this.digest.update(entropy);
            wipe(wipeBuffer);
        }

        this.digest.update(TIME_ENTROPY_SOURCE.provideEntropy());
        this.digest.update(this.seedBuffer);
        this.seedBuffer.clear();
        // remove data from seedBuffer so it won't be retrievable

        // reuse

        try {
            this.digest.digest(this.seedBuffer.array(), 0,
                    this.seedBuffer.capacity());
        } catch (final DigestException e) {
            throw new IllegalStateException(
                    "DigestException should not be thrown", e);
        }
        secureRandom.setSeed(this.seedBuffer.array());

        wipe(this.seedBuffer);
    }

    private void wipe(final ByteBuffer buf) {
        while (buf.hasRemaining()) {
            buf.put((byte) 0);
        }
    }
}

这是一个名为EntropySource的小接口:

/**
 * A simple interface that can be used to retrieve entropy from any source.
 * 
 * @author owlstead
 */
public interface EntropySource {
    /**
     * Retrieves the entropy.
     * The position of the ByteBuffer must be advanced to the limit by any users calling this method.
     * The values of the bytes between the position and limit should be set to zero by any users calling this method.
     * 
     * @return entropy within the position and limit of the given buffer
     */
    ByteBuffer provideEntropy();
}

请注意,这些类的输出未经过随机性测试(但这主要取决于返回的SecureRandom类,因此应该没有问题)。
最后,由于我没有准备好Android 1.6运行时,有人应该针对此版本或更低版本进行兼容性测试!

在向ByteBuffer中添加随机数据后,小心使用flip()方法。 - Maarten Bodewes
我的回答有什么不足之处吗,Hampus? - Maarten Bodewes
1
Google的CSPRNG补丁在所有Android版本中都放弃了SHA1PRNG,转而直接从/dev/urandom读取,除了4.2 / 4.3已经这样做(但搞砸了初始种子)。他们使用了比时间更多的熵源。不幸的是,他们通过写入/dev/urandom进行种子生成,在某些CyanogenMod和最新的Galaxy S4固件上失败,因为它们拒绝写入。在您的代码和由内核随时间重新播种的/dev/urandom之间,我不知道该使用什么了。 - Barend

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接