Android.bp 0100644 0000000 0000000 00000001471 13513561470 011520 0 ustar 00 0000000 0000000 // // Copyright (C) 2017 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // java_library_static { name: "rappor", no_framework_libs: true, java_version: "1.7", sdk_version: "core_current", libs: [ "jsr305", ], srcs: ["client/java/**/*.java"], } Android.mk 0100644 0000000 0000000 00000001672 13513561470 011531 0 ustar 00 0000000 0000000 # # Copyright (C) 2017 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := rappor-tests LOCAL_JAVA_LANGUAGE_VERSION := 1.7 LOCAL_STATIC_JAVA_LIBRARIES := hamcrest-library rappor guava junit LOCAL_JAVA_LIBRARIES := jsr305 LOCAL_SDK_VERISON := core_current LOCAL_SRC_FILES := $(call all-java-files-under, client/javatest) include $(BUILD_STATIC_JAVA_LIBRARY) LICENSE 0100644 0000000 0000000 00000026136 13513561470 010627 0 ustar 00 0000000 0000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. MODULE_LICENSE_APACHE2 0100644 0000000 0000000 00000000000 13513561470 012735 0 ustar 00 0000000 0000000 NOTICE 0100644 0000000 0000000 00000026136 13513561470 010526 0 ustar 00 0000000 0000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. README.android 0100644 0000000 0000000 00000001071 13513561470 012110 0 ustar 00 0000000 0000000 This project only contains Rappor java client library. If you want to get the full source or other components, please visit Rappor github page: https://github.com/google/rappor Any Android specific modifications to upstream archive-patcher should be listed here: - Removed all non-java client library files. - Modified client/java/com/google/rappor/HmacDrbg.java, client/java/com/google/rappor/Encoder.java to remove Guava, javax dependency. - Moved package from com.google.rappor.* to com.google.android.rappor.* to avoid source conflict with 3rd party library. README.md 0100644 0000000 0000000 00000002763 13513561470 011101 0 ustar 00 0000000 0000000 RAPPOR ====== RAPPOR is a novel privacy technology that allows inferring statistics about populations while preserving the privacy of individual users. This repository contains simulation and analysis code in Python and R. For a detailed description of the algorithms, see the [paper](http://arxiv.org/abs/1407.6981) and links below. Feel free to send feedback to [rappor-discuss@googlegroups.com][group]. ------------- - [RAPPOR Data Flow](http://google.github.io/rappor/doc/data-flow.html) Publications ------------ - [RAPPOR: Randomized Aggregatable Privacy-Preserving Ordinal Response](http://arxiv.org/abs/1407.6981) - [Building a RAPPOR with the Unknown: Privacy-Preserving Learning of Associations and Data Dictionaries](http://arxiv.org/abs/1503.01214) Links ----- - [Google Blog Post about RAPPOR](http://googleresearch.blogspot.com/2014/10/learning-statistics-with-privacy-aided.html) - [RAPPOR implementation in Chrome](http://www.chromium.org/developers/design-documents/rappor) - This is a production quality C++ implementation, but it's somewhat tied to Chrome, and doesn't support all privacy parameters (e.g. only a few values of p and q). On the other hand, the code in this repo is not yet production quality, but supports experimentation with different parameters and data sets. Of course, anyone is free to implement RAPPOR independently as well. - Mailing list: [rappor-discuss@googlegroups.com][group] [group]: https://groups.google.com/forum/#!forum/rappor-discuss README.version 0100644 0000000 0000000 00000000222 13513561470 012152 0 ustar 00 0000000 0000000 URL: https://github.com/google/rappor Version: a13fa964edb7c576366c83f40ff58d7c8c1db759 BugComponent: 315013 Owners: rickywai, pvisontay, simonjw client/ 0040755 0000000 0000000 00000000000 13513561470 011073 5 ustar 00 0000000 0000000 client/README.md 0100644 0000000 0000000 00000002126 13513561470 012350 0 ustar 00 0000000 0000000 RAPPOR Clients ============== This directory contains RAPPOR client implementations in various languages. The privacy of RAPPOR is based on the client "lying" about the true values -- that is, not sending them over the network. The clients are typically small in terms of code size because the RAPPOR client algorithm is simple. See the README.md in each subdirectory for details on how to use the library. Common Test Protocol -------------------- When implementing a new RAPPOR client, you can get for free! The `regtest.sh` script in the root of this repository does the following: 1. Create test input data and feed it into your client as a CSV file 2. Preprocesses your client output (also CSV) 3. Runs the RAPPOR analysis, learning aggregate statistics from encoded values 4. Compares the analysis to the true client values, with metrics and plots. To have your client tested, you need a small executable wrapper, which reads and write as CSV file in a specified format. Then add it to the `_run-one-instance` function in `regtest.sh`. client/java/ 0040755 0000000 0000000 00000000000 13513561470 012014 5 ustar 00 0000000 0000000 client/java/com/ 0040755 0000000 0000000 00000000000 13513561470 012572 5 ustar 00 0000000 0000000 client/java/com/google/ 0040755 0000000 0000000 00000000000 13513561470 014046 5 ustar 00 0000000 0000000 client/java/com/google/android/ 0040755 0000000 0000000 00000000000 13513561470 015466 5 ustar 00 0000000 0000000 client/java/com/google/android/rappor/ 0040755 0000000 0000000 00000000000 13513561470 016771 5 ustar 00 0000000 0000000 client/java/com/google/android/rappor/Encoder.java 0100644 0000000 0000000 00000053703 13513561470 021220 0 ustar 00 0000000 0000000 package com.google.android.rappor; // BEGIN android-changed: Removed guava dependency // import static com.google.common.base.Preconditions.checkArgument; // // import com.google.common.base.Verify; // END android-changed import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.BitSet; // BEGIN android-changed import java.util.Random; // END android-changed // BEGIN android-changed: Remove javax // import javax.annotation.Nullable; // import javax.annotation.concurrent.GuardedBy; // END android-changed /** * Encodes reports using the RAPPOR differentially-private encoding algorithm. * * The RAPPOR algorithm is described at go/rappor and presented in detail at go/rappor-writeup. * * @author bonawitz@google.com Keith Bonawitz */ // TODO(bonawitz): Make encoder and interface and make this a final class implementing it. // We can't just make this final now because there exist tests that need to Mock it. public class Encoder { /** * A non-decreasing version number. * *
The version number should increase any time the Encoder has a user-visible functional change * to any of encoding algorithms or the interpretation of the input parameters. */ public static final long VERSION = 3; /** * Minimum length required for the user secret, in bytes. */ public static final int MIN_USER_SECRET_BYTES = HmacDrbg.ENTROPY_INPUT_SIZE_BYTES; /** * Maximum number of bits in the RAPPOR-encoded report. * * Must be less than HmacDrbg.MAX_BYTES_TOTAL; */ public static final int MAX_BITS = 4096; /** * Maximum number of Bloom filter hashes used for RAPPOR-encoded strings. * *
This is constrained in the current implementation by requiring 2 bytes from an MD5 value * (which is 16 bytes long) for each Bloom filter hash. */ public static final int MAX_BLOOM_HASHES = 8; /** * Maximum number of cohorts supported. */ public static final int MAX_COHORTS = 128; /** * First (and only) byte of HMAC_DRBG personalization strings used to generate the cohort number. */ private static final byte HMAC_DRBG_TYPE_COHORT = 0x00; /** * First byte of HMAC_DRBG personalization strings used to generate the PRR response. */ private static final byte HMAC_DRBG_TYPE_PRR = 0x01; /** * A unique identifier for this Encoder, represented in UTF-8. * *
The (userSecret, encoderId) pair identify a the logical memoization table used for RAPPOR's * Permanent Randomized Response stage. Therefore, for any userSecret, each Encoder must have a * distinct identifier for Permanent Randomized Response to be effective. * *
In practice, "memoization" is achieved by generating deterministic pseudo-random bits using * HMAC_DRBG. encoderIdBytes is used as part of the personalization string. */ private final byte[] encoderIdBytes; /** * The RAPPOR "f" probability, on the range [0.0, 1.0]. * *
This it the probability of replacing a bit from the input with a uniform random bit when * generating the permanent randomized response. * *
Setting probabilityF to 0 disables the PRR phase of RAPPOR. */ private final double probabilityF; /** * The RAPPOR "p" probability, on the range [0.0, 1.0]. * *
This is the probability of emitting a '1' bit in the instantaneous randomized response, * given that the corresponding bit in the permanent randomized response is '0'. * *
Setting probabilityP to 0.0 and probabilityQ to 1.0 disables the IRR phase of RAPPOR. */ private final double probabilityP; /** * The RAPPOR "1" probability, on the range [0.0, 1.0]. * *
This is the probability of emitting a 1 bit in the instantaneous randomized response, given * that the corresponding bit in the permanent randomized response is 1. * *
Setting probabilityP to 0.0 and probabilityQ to 1.0 disables the IRR phase of RAPPOR. */ private final double probabilityQ; /** * The number of bits in the RAPPOR-encoded report. * *
Must satisfy 1 <= numBits <= MAX_BITS. * *
Must satisfy 1 <= numBloomHashes <= MAX_BLOOM_HASHES. */ private final int numBloomHashes; /** * The number of cohorts used for cohort assignment. */ private final int numCohorts; /** * The cohort this user is assigned to for Bloom filter string encoding. * *
This value is stable for a given (userSecret, numCohorts) pair. Furthermore, if two * encoders use the same userSecret but have different numCohorts values, the cohort assignment * for the encoder with the smaller numCohorts value is a bitwise suffix of the cohort assignment * for the encoder with the larger numCohorts value. It follows that, if you know maximum cohort * assignment across a set of encoders, and you know the numCohorts setting for each encoder, then * you can deduce the cohort assignment for each encoder by taking the bitwise-and of the max * cohort value and (numCohorts-1), noting that numCohorts is required to be a positive power of * 2. */ private final int cohort; /** * A bitmask with 1 bits in all the positions where a RAPPOR-encoded report could have a 1 bit. * *
The bitmask has the lowest order numBits set to 1 and the rest 0. */ private final BitSet inputMask; /** * SHA-256 utility class instance. * *
This object is stateful; access must be synchronized. The reset method must be * called before each use. */ // BEGIN android-changed: Remove javax // @GuardedBy("this") // END android-changed private final MessageDigest sha256; /** * MD5 utility class instance. * *
This object is stateful; access must be synchronized. The reset method must be * called before each use. */ // BEGIN android-changed: Remove javax // @GuardedBy("this") // END android-changed private final MessageDigest md5; /** * A SecureRandom instance, initialized with a cryptographically secure random seed. */ // BEGIN android-changed // private final SecureRandom random; private final Random random; // BEGIN android-changed /** * Entropy input for constructing HmacDrbg objects. */ private final byte[] userSecret; /** * Constructs a new RAPPOR message encoder. * * @param userSecret Stable secret randomly selected for this user. UserSecret must be at least * MIN_USER_SECRET_BYTES bytes of high-quality entropy. Changing the user secret clears the * memoized cohort assignments and permanent randomized responses. Be aware that resetting * these memoizations has significant privacy risks -- consult documentation at go/rappor for * more details. * @param encoderId Uniquely identifies this encoder. Used to differentiate momoized * cohort assignments and permanent randomized responses. * @param numBits The number of bits in the RAPPOR-encoded report. * @param probabilityF The RAPPOR "f" probability, on the range [0.0, 1.0]. This will be * quantized to the nearest 1/128. * @param probabilityP The RAPPOR "p" probability, on the range [0.0, 1.0]. * @param probabilityQ The RAPPOR "1" probability, on the range [0.0, 1.0]. * @param numCohorts Number of cohorts into which the user pool is randomly segmented. * @param numBloomHashes The number of hash functions used forming the Bloom filter encoding of a * string. */ public Encoder(byte[] userSecret, String encoderId, int numBits, double probabilityF, double probabilityP, double probabilityQ, int numCohorts, int numBloomHashes) { this( null, // random null, // md5, null, // sha256, userSecret, encoderId, numBits, probabilityF, probabilityP, probabilityQ, numCohorts, numBloomHashes); } /** * Constructs a new RAPPOR message encoder, using constructor-style dependency injection. * * @param random A cryptographically secure random number generator, or null (in which case a * new SecureRandom will be constructed). * @param md5 A configured MD5 hash algorithm, or null (in which case a new MessageDigest will be * constructed). Note: MessageDigest objects are stateful, and that state must not be * modified while calls to the Encoder are active. * @param sha256 A configured SHA-256 hash algorithm, or null (in which case a new MessageDigest * will be constructed). Note: MessageDigest objects are stateful, and that state must not * be modified while calls to the Encoder are active. * @param userSecret Stable secret randomly selected for this user. UserSecret must be at least * 32 bytes of high-quality entropy. Changing the user secret clears the memoized cohort * assignments and permanent randomized responses. Be aware that resetting these memoizations * has significant privacy risks -- consult documentation at go/rappor for more details. * @param encoderId Uniquely identifies this encoder. Used to differentiate momoized * cohort assignments and permanent randomized responses. * @param numBits The number of bits in the RAPPOR-encoded report. * @param probabilityF The RAPPOR "f" probability, on the range [0.0, 1.0]. This will be * quantized to the nearest 1/128. * @param probabilityP The RAPPOR "p" probability, on the range [0.0, 1.0]. * @param probabilityQ The RAPPOR "1" probability, on the range [0.0, 1.0]. * @param numCohorts Number of cohorts into which the user pool is randomly segmented. * @param numBloomHashes The number of hash functions used forming the Bloom filter encoding of a * string. */ public Encoder( // BEGIN android-changed: Remove javax // @Nullable SecureRandom random, // @Nullable MessageDigest md5, // @Nullable MessageDigest sha256, // SecureRandom random, Random random, MessageDigest md5, MessageDigest sha256, // END android-changed byte[] userSecret, String encoderId, int numBits, double probabilityF, double probabilityP, double probabilityQ, int numCohorts, int numBloomHashes) { if (md5 != null) { this.md5 = md5; } else { try { this.md5 = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException impossible) { // This should never happen. Every implementation of the Java platform // is required to support MD5. throw new AssertionError(impossible); } } this.md5.reset(); if (sha256 != null) { this.sha256 = sha256; } else { try { this.sha256 = MessageDigest.getInstance("SHA-256"); } catch (NoSuchAlgorithmException impossible) { // This should never happen. Every implementation of the Java platform // is required to support 256. throw new AssertionError(impossible); } } this.sha256.reset(); this.encoderIdBytes = encoderId.getBytes(StandardCharsets.UTF_8); if (random != null) { this.random = random; } else { this.random = new SecureRandom(); } // android-changed: Removed guava dependency // checkArgument( // userSecret.length >= MIN_USER_SECRET_BYTES, // "userSecret must be at least %s bytes of high-quality entropy.", // MIN_USER_SECRET_BYTES); checkArgument( userSecret.length >= MIN_USER_SECRET_BYTES, "userSecret must be at least " + MIN_USER_SECRET_BYTES + " bytes of high-quality entropy."); this.userSecret = userSecret; checkArgument( probabilityF >= 0 && probabilityF <= 1, "probabilityF must be on range [0.0, 1.0]"); this.probabilityF = Math.round(probabilityF * 128) / 128.0; checkArgument( probabilityP >= 0 && probabilityP <= 1, "probabilityP must be on range [0.0, 1.0]"); this.probabilityP = probabilityP; checkArgument( probabilityQ >= 0 && probabilityQ <= 1, "probabilityQ must be on range [0.0, 1.0]"); this.probabilityQ = probabilityQ; checkArgument( numBits >= 1 && numBits <= MAX_BITS, "numBits must be on range [1, " + MAX_BITS + "]."); this.numBits = numBits; // Make a bitmask with the lowest-order numBits set to 1. this.inputMask = new BitSet(numBits); this.inputMask.set(0, numBits, true); checkArgument( numBloomHashes >= 1 && numBloomHashes <= numBits, "numBloomHashes must be on range [1, numBits)."); this.numBloomHashes = numBloomHashes; checkArgument( numCohorts >= 1 && numCohorts <= MAX_COHORTS, "numCohorts must be on range [1, " + MAX_COHORTS + "]."); // Assuming numCohorts >= 1: // // If numCohorts is a power of 2, then numCohorts has one bit set and numCohorts - 1 has only // the bits to the right of numCohorts's bit set. The bitwise-and of these is 0. // // If numCohorts is not a power of 2, then numCohorts has at least two bits set. It follows // subtracting one from numCohorts keeps the most significant bit set to 1, and thus the // bitwise-and has at least this non-zero bit. boolean numCohortsIsPowerOfTwo = (numCohorts & (numCohorts - 1)) == 0; checkArgument(numCohortsIsPowerOfTwo, "numCohorts must be a power of 2."); this.numCohorts = numCohorts; // cohortMasterAssignment depends only on the userSecret. HmacDrbg cohortDrbg = new HmacDrbg(userSecret, new byte[] {HMAC_DRBG_TYPE_COHORT}); ByteBuffer cohortDrbgBytes = ByteBuffer.wrap(cohortDrbg.nextBytes(4)); int cohortMasterAssignment = Math.abs(cohortDrbgBytes.getInt()) % MAX_COHORTS; this.cohort = cohortMasterAssignment & (numCohorts - 1); } public double getProbabilityF() { return probabilityF; } public double getProbabilityP() { return probabilityP; } public double getProbabilityQ() { return probabilityQ; } public int getNumBits() { return numBits; } public int getNumBloomHashes() { return numBloomHashes; } public int getNumCohorts() { return numCohorts; } public int getCohort() { return cohort; } /** * Returns the Encoder ID as a UTF-8 formatted string. */ public String getEncoderId() { return new String(encoderIdBytes, StandardCharsets.UTF_8); } /** * Encodes a boolean into a RAPPOR report. * *
The boolean is 0 or 1, then encoded using permanent and instantaneous randomized response. * *
In most cases, numBits should be 1 when using this method. */ public byte[] encodeBoolean(boolean bool) { BitSet input = new BitSet(numBits); input.set(0, bool); return encodeBits(input); } /** * Encodes an ordinal into a RAPPOR report. * *
The ordinal is represented using a 1-hot binary representation, then encoded using permanent * and instantaneous randomized response. * * @param ordinal A value on the range [0, numBits). */ public byte[] encodeOrdinal(int ordinal) { checkArgument( ordinal >= 0 && ordinal < numBits, "Ordinal value must be in range [0, numBits)."); BitSet input = new BitSet(numBits); input.set(ordinal, true); return encodeBits(input); } /** * Encodes a string into a RAPPOR report. * *
The string is represented using a Bloom filter with numBloomHashes hash functions, then * encoded using permanent and instantaneous randomized response. * * @param string An arbitrary string. */ public byte[] encodeString(String string) { // Implements a Bloom filter by slicing a single MD5 hash into numBloomHashes bit indices. byte[] stringInUtf8 = string.getBytes(StandardCharsets.UTF_8); byte[] message = ByteBuffer.allocate(4 + stringInUtf8.length) .putInt(cohort) .put(stringInUtf8) .array(); byte[] digest; synchronized (this) { md5.reset(); digest = md5.digest(message); } // android-changed: Removed guava dependency // Verify.verify(digest.length == 16); // Verify.verify(numBloomHashes <= digest.length / 2); verify(digest.length == 16); verify(numBloomHashes <= digest.length / 2); BitSet input = new BitSet(numBits); for (int i = 0; i < numBloomHashes; i++) { // Convert byte pairs to ints on [0, 65535]. // Anding with 0xFF converts signed byte to unsigned int. int digestWord = (digest[i * 2] & 0xFF) * 256 + (digest[i * 2 + 1] & 0xFF); int chosenBit = digestWord % numBits; input.set(chosenBit, true); } return encodeBits(input); } /** * Encodes an arbitrary bitstring into a RAPPOR report. * * @param bits A bitstring in which only the least significant numBits bits may be 1. */ public byte[] encodeBits(byte[] bits) { return encodeBits(BitSet.valueOf(bits)); } /** * Encodes an arbitrary bitstring into a RAPPOR report. * * @param bits A bitstring in which only the least significant numBits bits may be 1. */ private byte[] encodeBits(BitSet bits) { BitSet permanentRandomizedResponse = computePermanentRandomizedResponse(bits); BitSet encodedBitSet = computeInstantaneousRandomizedResponse(permanentRandomizedResponse); // BitSet.toByteArray only returns enough bytes to capture the most significant bit // that is set. For example, a BitSet with no bits set could return a length-0 array. // In contrast, we guarantee that our output is sized according to numBits. byte[] encodedBytes = encodedBitSet.toByteArray(); byte[] output = new byte[(numBits + 7) / 8]; // android-changed: Removed guava dependency // Verify.verify(encodedBytes.length <= output.length); verify(encodedBytes.length <= output.length); System.arraycopy( encodedBytes, // src 0, // srcPos output, // dest 0, // destPos encodedBytes.length); // length return output; } /** * Returns the permanent randomized response for the given bits. * *
The response for a particular bits input is guaranteed to always be the same for any encoder * constructed with the same parameters (including the encoderId and the userSecret). */ private BitSet computePermanentRandomizedResponse(BitSet bits) { // Ensures that the input only has bits set in the lowest BitSet masked = new BitSet(); masked.or(bits); masked.andNot(inputMask); checkArgument(masked.isEmpty(), "Input bits had bits set past Encoder's numBits limit."); if (probabilityF == 0.0) { return bits; } // Builds a personalization string that contains both the encoderId and input value (bits), // and is no longer than HmacDrbg.MAX_PERSONALIZATION_STRING_LENGTH_BYTES. The first byte // of the personalization string is always HMAC_DRBG_TYPE_PRR, to avoid collisions with the // cohort-generation personalization string. byte[] personalizationString; synchronized (this) { int personalizationStringLength = Math.min(HmacDrbg.MAX_PERSONALIZATION_STRING_LENGTH_BYTES, 1 + sha256.getDigestLength()); personalizationString = new byte[personalizationStringLength]; personalizationString[0] = HMAC_DRBG_TYPE_PRR; sha256.reset(); sha256.update(encoderIdBytes); sha256.update(new byte[] {0}); sha256.update(bits.toByteArray()); byte[] digest = sha256.digest(personalizationString); System.arraycopy(digest, 0, personalizationString, 1, personalizationString.length - 1); } HmacDrbg drbg = new HmacDrbg(userSecret, personalizationString); byte[] pseudorandomStream = drbg.nextBytes(numBits); // android-changed: Removed guava dependency // Verify.verify(numBits <= pseudorandomStream.length); verify(numBits <= pseudorandomStream.length); int probabilityFTimes128 = (int) Math.round(probabilityF * 128); BitSet result = new BitSet(numBits); for (int i = 0; i < numBits; i++) { // Grabs a single byte from the pseudorandom stream. // Anding with 0xFF converts a signed byte to an unsigned integer. int pseudorandomByte = pseudorandomStream[i] & 0xFF; // Uses bits 1-7 to get a random number between 0 and 127. int uniform0to127 = pseudorandomByte >> 1; boolean shouldUseNoise = uniform0to127 < probabilityFTimes128; if (shouldUseNoise) { // Uses bit 0 as a flip of a fair coin. result.set(i, (pseudorandomByte & 0x01) > 0); } else { result.set(i, bits.get(i)); } } return result; } /** * Returns the instantaneous randomized response for the given bits. * *
The instantaneous response is NOT memoized -- it is sampled randomly on * every invocation. */ private BitSet computeInstantaneousRandomizedResponse(BitSet bits) { // Ensures that the input only has bits set in the lowest BitSet masked = new BitSet(); masked.or(bits); masked.andNot(inputMask); checkArgument(masked.isEmpty(), "Input bits had bits set past Encoder's numBits limit."); if (probabilityP == 0.0 && probabilityQ == 1.0) { return bits; } BitSet response = new BitSet(numBits); for (int i = 0; i < numBits; i++) { boolean bit = bits.get(i); double probability = bit ? probabilityQ : probabilityP; boolean responseBit = random.nextFloat() < probability; response.set(i, responseBit); } return response; } // BEGIN android-changed: Added guava methods private static void checkArgument(boolean expression, Object errorMessage) { if (!expression) { throw new IllegalArgumentException(String.valueOf(errorMessage)); } } private static void verify(boolean expression) { if (!expression) { throw new java.lang.IllegalStateException(); } } // END android-changed } client/java/com/google/android/rappor/HmacDrbg.java 0100644 0000000 0000000 00000020501 13513561470 021276 0 ustar 00 0000000 0000000 package com.google.android.rappor; // BEGIN android-changed: Removed guava dependency // import com.google.common.hash.HashFunction; // import com.google.common.hash.Hashing; // import com.google.common.primitives.Bytes; // END android-changed import java.security.SecureRandom; import java.util.Arrays; import javax.annotation.concurrent.NotThreadSafe; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; /** * Deterministic Random Bit Generator based on HMAC-SHA256. * * Also known as: HMAC_DRBG. * See http://csrc.nist.gov/publications/nistpubs/800-90A/SP800-90A.pdf for thorough specification. * * Reseeding is not supported. Instead, construct a new DRBG when reseeding is required. * See http://csrc.nist.gov/publications/nistpubs/800-90A/SP800-90A.pdf Section 8.6.8. */ @NotThreadSafe public class HmacDrbg { // "V" from the the spec. private byte[] value; // BEGIN android-changed // An instance of HMAC-SHA256 configured with "Key" from the spec. // private HashFunction hmac; private Mac hmac; // END android-changed // The total number of bytes that have been generated from this DRBG so far. private int bytesGenerated; // Assume maximum security strength for HMAC-256, which is 256. // See: http://csrc.nist.gov/publications/nistpubs/800-90A/SP800-90A.pdf D.2 #1. public static final int SECURITY_STRENGTH = 256; /** * Personalization strings should not exceed this many bytes in length. * * See: http://csrc.nist.gov/publications/nistpubs/800-90A/SP800-90A.pdf D.2 #7. */ public static final int MAX_PERSONALIZATION_STRING_LENGTH_BYTES = 160 / 8; /** * The constructor's entropyInput should contain this many high quality random bytes. * HMAC_DRBG requires entropy input to be security_strength bits long, * and nonce to be at least 1/2 security_strength bits long. We * generate them both as a single "extra strong" entropy input. * See: http://csrc.nist.gov/publications/nistpubs/800-90A/SP800-90A.pdf */ public static final int ENTROPY_INPUT_SIZE_BYTES = (SECURITY_STRENGTH / 8) * 3 / 2; /** * The maximum total number of bytes that can be generated from this DRBG. * * This is conservative releative to the suggestions in * http://csrc.nist.gov/publications/nistpubs/800-90A/SP800-90A.pdf section D.2, * ensuring that reseeding is never triggered (each call to Generate produces at least one byte, * therefore MAX_BYTES will be reached before RESEED_INTERAL=10000 is exceeded) * and simplifying the interface (so that the client need not worry about MAX_BYTES_PER_REQUEST, * below. */ public static final int MAX_BYTES_TOTAL = 10000; // See: http://csrc.nist.gov/publications/nistpubs/800-90A/SP800-90A.pdf D.2 #2. private static final int DIGEST_NUM_BYTES = 256 / 8; // floor(7500/8); see: http://csrc.nist.gov/publications/nistpubs/800-90A/SP800-90A.pdf D.2 #5. private static final int MAX_BYTES_PER_REQUEST = 937; private static final byte[] BYTE_ARRAY_0 = {0}; private static final byte[] BYTE_ARRAY_1 = {1}; public HmacDrbg(byte[] entropyInput, byte[] personalizationString) { // HMAC_DRBG Instantiate Process // See: http://csrc.nist.gov/publications/nistpubs/800-90A/SP800-90A.pdf 10.1.1.2 // 1. seed_material = entropy_input + nonce + personalization_string // Note: We are using the 8.6.7 interpretation, where the entropy_input and // nonce are acquired at the same time from the same source. // BEGIN android-changed // byte[] seedMaterial = Bytes.concat(entropyInput, emptyIfNull(personalizationString)); byte[] seedMaterial = bytesConcat(entropyInput, emptyIfNull(personalizationString)); // END android-changed // 2. Key = 0x00 00...00 setKey(new byte[256 / 8]); // 3. V = 0x01 01...01 value = new byte[DIGEST_NUM_BYTES]; Arrays.fill(value, (byte) 0x01); // 4. (Key, V) = HMAC_DRBG_Update(seed_material, Key, V) hmacDrbgUpdate(seedMaterial); bytesGenerated = 0; } /** * Returns an 0-length byte array if b is null, otherwise returns b. */ private static byte[] emptyIfNull(byte[] b) { return b == null ? new byte[0] : b; } /** * Set's the "Key" state from the spec. */ private void setKey(byte[] key) { // BEGIN android-changed // hmac = Hashing.hmacSha256(key); try { hmac = Mac.getInstance("HmacSHA256"); SecretKeySpec secretKey = new SecretKeySpec(key, "HmacSHA256"); hmac.init(secretKey); } catch (Exception e) {} // END android-changed } /** * Computes hmac("key" from the spec, x). */ private byte[] hash(byte[] x) { // BEGIN android-changed // return hmac.hashBytes(x).asBytes(); try { return hmac.doFinal(x); } catch (Exception e) { return null; } // END android-changed } /** * HMAC_DRBG Update Process * * See: http://csrc.nist.gov/publications/nistpubs/800-90A/SP800-90A.pdf 10.1.2.2 */ private void hmacDrbgUpdate(byte[] providedData) { // 1. K = HMAC(K, V || 0x00 || provided_data) // BEGIN android-changed // setKey(hash(Bytes.concat(value, BYTE_ARRAY_0, emptyIfNull(providedData)))); setKey(hash(bytesConcat(value, BYTE_ARRAY_0, emptyIfNull(providedData)))); // END android-changed // 2. V = HMAC(K, V); value = hash(value); // 3. If (provided_data = Null), then return K and V. if (providedData == null) { return; } // 4. K = HMAC (K, V || 0x01 || provided_data). // BEGIN android-changed // setKey(hash(Bytes.concat(value, BYTE_ARRAY_1, providedData))); setKey(hash(bytesConcat(value, BYTE_ARRAY_1, providedData))); // END android-changed // 5. V = HMAC (K, V). value = hash(value); } /** * HMAC_DRBG Generate Process * * See: http://csrc.nist.gov/publications/nistpubs/800-90A/SP800-90A.pdf 10.1.2.5 * * We do not support additional_input, assuming it to be always null. * * We guarantee that reseeding is never required through the use of MAX_BYTES_TOTAL * rather than RESEED_INTERVAL. */ private void hmacDrbgGenerate(byte[] out, int start, int count) { // 3. temp = Null. int bytesWritten = 0; // 4. While (len (temp) < requested_number_of_bits) do: while (bytesWritten < count) { // 4.1 V = HMAC (Key, V). value = hash(value); // 4.2 temp = temp || V. // 5. returned_bits = Leftmost requested_number_of_bits of temp int bytesToWrite = Math.min(count - bytesWritten, DIGEST_NUM_BYTES); System.arraycopy(value, 0, out, start + bytesWritten, bytesToWrite); bytesWritten += bytesToWrite; } // 6. (Key, V) = HMAC_DRBG_Update (additional_input, Key, V). hmacDrbgUpdate(null); } /** * Generates entropy byte-string suitable for use as the constructor's entropyInput. * * Uses SecureRandom to generate entropy. */ public static byte[] generateEntropyInput() { byte result[] = new byte[ENTROPY_INPUT_SIZE_BYTES]; new SecureRandom().nextBytes(result); return result; } /** * Returns the next length pseudo-random bytes. */ public byte[] nextBytes(int length) { byte result[] = new byte[length]; nextBytes(result); return result; } /** * Fills the output vector with pseudo-random bytes. */ public void nextBytes(byte[] out) { nextBytes(out, 0, out.length); } /** * Fills out[start] through out[start+count-1] (inclusive) with pseudo-random bytes. */ public void nextBytes(byte[] out, int start, int count) { if (count == 0) { return; } if (bytesGenerated + count > MAX_BYTES_TOTAL) { throw new IllegalStateException("Cannot generate more than a total of " + count + " bytes."); } try { int bytesWritten = 0; while (bytesWritten < count) { int bytesToWrite = Math.min(count - bytesWritten, MAX_BYTES_PER_REQUEST); hmacDrbgGenerate(out, start + bytesWritten, bytesToWrite); bytesWritten += bytesToWrite; } } finally { bytesGenerated += count; } } // BEGIN android-changed private static byte[] bytesConcat(byte[]... arrays) { int length = 0; for (byte[] array : arrays) { length += array.length; } byte[] result = new byte[length]; int pos = 0; for (byte[] array : arrays) { System.arraycopy(array, 0, result, pos, array.length); pos += array.length; } return result; } // END android-changed } client/javatest/ 0040755 0000000 0000000 00000000000 13513561470 012714 5 ustar 00 0000000 0000000 client/javatest/com/ 0040755 0000000 0000000 00000000000 13513561470 013472 5 ustar 00 0000000 0000000 client/javatest/com/google/ 0040755 0000000 0000000 00000000000 13513561470 014746 5 ustar 00 0000000 0000000 client/javatest/com/google/android/ 0040755 0000000 0000000 00000000000 13513561470 016366 5 ustar 00 0000000 0000000 client/javatest/com/google/android/rappor/ 0040755 0000000 0000000 00000000000 13513561470 017671 5 ustar 00 0000000 0000000 client/javatest/com/google/android/rappor/EncoderTest.java 0100644 0000000 0000000 00000110436 13513561470 022755 0 ustar 00 0000000 0000000 package com.google.android.rappor; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.lessThanOrEqualTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThat; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.junit.runners.BlockJUnit4ClassRunner; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; // BEGIN android-changed // import java.security.SecureRandom; import java.util.Random; // END android-changed /** * Unit tests for {@link Encoder}. */ @RunWith(BlockJUnit4ClassRunner.class) public class EncoderTest { @Rule public final ExpectedException thrown = ExpectedException.none(); /** * Convert a human readable string to a 32 byte userSecret for testing. * *
Do not use this in a production environment! For security, userSecret * must be at least 32 bytes of high-quality entropy. */ private static byte[] makeTestingUserSecret(String testingSecret) throws Exception { // We generate the fake user secret by concatenating three copies of the // 16 byte MD5 hash of the testingSecret string encoded in UTF 8. MessageDigest md5 = MessageDigest.getInstance("MD5"); byte[] digest = md5.digest(testingSecret.getBytes(StandardCharsets.UTF_8)); assertEquals(16, digest.length); return ByteBuffer.allocate(48).put(digest).put(digest).put(digest).array(); } private static long toLong(byte[] bytes) { assertThat(bytes.length, is(lessThanOrEqualTo(8))); ByteBuffer buffer = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).put(bytes); buffer.rewind(); // can't chain rewind() because it returns Buffer, not ByteBuffer. return buffer.getLong(); } private static byte[] toBytes(long value) { return ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(value).array(); } @Test public void testEncoderConstruction_goodArguments() throws Exception { // Full RAPPOR new Encoder(makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 8, // numBits, 13.0 / 128.0, // probabilityF 0.25, // probabilityP 0.75, // probabilityQ 1, // numCohorts 2); // numBloomHashes // IRR-only (no PRR) new Encoder(makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 8, // numBits, 0, // probabilityF 0.25, // probabilityP 0.75, // probabilityQ 1, // numCohorts 2); // numBloomHashes // PRR-only (no IRR) new Encoder(makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 8, // numBits, 13.0 / 128.0, // probabilityF 0.0, // probabilityP 1.0, // probabilityQ 1, // numCohorts 2); // numBloomHashes } @Test public void testEncoderConstruction_userSecretTooShort() throws Exception { thrown.expect(IllegalArgumentException.class); byte[] tooShortSecret = new byte[47]; new Encoder(tooShortSecret, // userSecret "Foo", // encoderId 8, // numBits, 13.0 / 128.0, // probabilityF 0.25, // probabilityP 0.75, // probabilityQ 1, // numCohorts 2); // numBloomHashes } @Test public void testEncoderConstruction_userSecretMayBeLong() throws Exception { byte[] tooLongSecret = new byte[49]; new Encoder(tooLongSecret, // userSecret "Foo", // encoderId 8, // numBits, 13.0 / 128.0, // probabilityF 0.25, // probabilityP 0.75, // probabilityQ 1, // numCohorts 2); // numBloomHashes } @Test public void testEncoderConstruction_numBitsTooLow() throws Exception { thrown.expect(IllegalArgumentException.class); new Encoder(makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 0, // numBits, 13.0 / 128.0, // probabilityF 0.25, // probabilityP 0.75, // probabilityQ 1, // numCohorts 2); // numBloomHashes } @Test public void testEncoderConstruction_numBitsTooHigh() throws Exception { thrown.expect(IllegalArgumentException.class); new Encoder( makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 4097, // numBits, 13.0 / 128.0, // probabilityF 0.25, // probabilityP 0.75, // probabilityQ 1, // numCohorts 2); // numBloomHashes } @Test public void testEncoderConstruction_probabilityFTooLow() throws Exception { thrown.expect(IllegalArgumentException.class); new Encoder(makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 8, // numBits, -0.01, // probabilityF 0.25, // probabilityP 0.75, // probabilityQ 1, // numCohorts 2); // numBloomHashes } @Test public void testEncoderConstruction_probabilityFTooHigh() throws Exception { thrown.expect(IllegalArgumentException.class); new Encoder(makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 8, // numBits, 1.01, // probabilityF 0.25, // probabilityP 0.75, // probabilityQ 1, // numCohorts 2); // numBloomHashes } @Test public void testEncoderConstruction_probabilityPTooLow() throws Exception { thrown.expect(IllegalArgumentException.class); new Encoder(makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 8, // numBits, 13.0 / 128.0, // probabilityF -0.01, // probabilityP 0.75, // probabilityQ 1, // numCohorts 2); // numBloomHashes } @Test public void testEncoderConstruction_probabilityPTooHigh() throws Exception { thrown.expect(IllegalArgumentException.class); new Encoder(makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 8, // numBits, 13.0 / 128.0, // probabilityF 1.01, // probabilityP 0.75, // probabilityQ 1, // numCohorts 2); // numBloomHashes } @Test public void testEncoderConstruction_probabilityQTooLow() throws Exception { thrown.expect(IllegalArgumentException.class); new Encoder(makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 8, // numBits, 13.0 / 128.0, // probabilityF 0.25, // probabilityP -0.01, // probabilityQ 1, // numCohorts 2); // numBloomHashes } @Test public void testEncoderConstruction_probabilityQTooHigh() throws Exception { thrown.expect(IllegalArgumentException.class); new Encoder(makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 8, // numBits, 13.0 / 128.0, // probabilityF 0.75, // probabilityP 1.01, // probabilityQ 1, // numCohorts 2); // numBloomHashes } @Test public void testEncoderConstruction_numCohortsTooLow() throws Exception { thrown.expect(IllegalArgumentException.class); new Encoder(makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 8, // numBits, 13.0 / 128.0, // probabilityF 0.25, // probabilityP 0.75, // probabilityQ 0, // numCohorts 2); // numBloomHashes } @Test public void testEncoderConstruction_numCohortsTooHigh() throws Exception { thrown.expect(IllegalArgumentException.class); new Encoder(makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 8, // numBits, 13.0 / 128.0, // probabilityF 0.25, // probabilityP 0.75, // probabilityQ Encoder.MAX_COHORTS + 1, // numCohorts 2); // numBloomHashes } @Test public void testEncoderConstruction_numCohortsNotPowerOf2() throws Exception { thrown.expect(IllegalArgumentException.class); new Encoder(makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 8, // numBits, 13.0 / 128.0, // probabilityF 0.25, // probabilityP 0.75, // probabilityQ 3, // numCohorts 2); // numBloomHashes } @Test public void testEncoderConstruction_numBloomHashesTooLow() throws Exception { thrown.expect(IllegalArgumentException.class); new Encoder(makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 8, // numBits, 13.0 / 128.0, // probabilityF 0.25, // probabilityP 0.75, // probabilityQ 1, // numCohorts 0); // numBloomHashes } @Test public void testEncoderConstruction_numBloomHashesTooHigh() throws Exception { thrown.expect(IllegalArgumentException.class); new Encoder(makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 8, // numBits, 13.0 / 128.0, // probabilityF 0.25, // probabilityP 0.75, // probabilityQ 1, // numCohorts 9); // numBloomHashes } @Test public void testEncoderGetCohort() throws Exception { // This is a stable, random cohort assignment. assertEquals( 3, new Encoder(makeTestingUserSecret("Blotto"), // userSecret "Foo", // encoderId 8, // numBits, 13.0 / 128.0, // probabilityF 0.25, // probabilityP 0.75, // probabilityQ 4, // numCohorts 2) // numBloomHashes .getCohort()); // With numCohorts == 1, the only possible cohort assigment is 0. assertEquals( 0, new Encoder(makeTestingUserSecret("Blotto"), // userSecret "Foo", // encoderId 8, // numBits, 13.0 / 128.0, // probabilityF 0.25, // probabilityP 0.75, // probabilityQ 1, // numCohorts 2) // numBloomHashes .getCohort()); // Changing the user secret changes the cohort. assertEquals( 3, new Encoder( makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 8, // numBits, 13.0 / 128.0, // probabilityF 0.25, // probabilityP 0.75, // probabilityQ 4, // numCohorts 2) // numBloomHashes .getCohort()); assertEquals( 0, new Encoder( makeTestingUserSecret("Bar2"), // userSecret "Foo", // encoderId 8, // numBits, 13.0 / 128.0, // probabilityF 0.25, // probabilityP 0.75, // probabilityQ 4, // numCohorts 2) // numBloomHashes .getCohort()); // Changing the encoder id does not changes the cohort. assertEquals( 3, new Encoder(makeTestingUserSecret("Blotto"), // userSecret "Foo1", // encoderId 8, // numBits, 13.0 / 128.0, // probabilityF 0.25, // probabilityP 0.75, // probabilityQ 4, // numCohorts 2) // numBloomHashes .getCohort()); assertEquals( 3, new Encoder(makeTestingUserSecret("Blotto"), // userSecret "Foo2", // encoderId 8, // numBits, 13.0 / 128.0, // probabilityF 0.25, // probabilityP 0.75, // probabilityQ 4, // numCohorts 2) // numBloomHashes .getCohort()); assertEquals( 3, new Encoder(makeTestingUserSecret("Blotto"), // userSecret "Foo3", // encoderId 8, // numBits, 13.0 / 128.0, // probabilityF 0.25, // probabilityP 0.75, // probabilityQ 4, // numCohorts 2) // numBloomHashes .getCohort()); // Cohort assignments are bit-wise subsets int cohortAssignmentBig = new Encoder( makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 8, // numBits, 13.0 / 128.0, // probabilityF 0.25, // probabilityP 0.75, // probabilityQ Encoder.MAX_COHORTS, // numCohorts 2) // numBloomHashes .getCohort(); int numCohortsSmall = Encoder.MAX_COHORTS / 2; // Verify that numCohortsSmall is a power of 2. assertEquals(0, numCohortsSmall & (numCohortsSmall - 1)); int cohortAssignmentSmall = new Encoder( makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 8, // numBits, 13.0 / 128.0, // probabilityF 0.25, // probabilityP 0.75, // probabilityQ numCohortsSmall, // numCohorts 2) // numBloomHashes .getCohort(); // This validates that the test case is well chosen. If it fails, select a different userSecret // or encoderId. assertNotEquals(cohortAssignmentBig, cohortAssignmentSmall); // Test that cohortAssignmentSmall is a suffix of cohortAssignmentBig when represented in // binary. assertEquals(cohortAssignmentBig & (numCohortsSmall - 1), cohortAssignmentSmall); } @Test public void testEncoderEncodeBits_identity() throws Exception { assertEquals( 0b11111101L, toLong( new Encoder( makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 8, // numBits, 0, // probabilityF 0, // probabilityP 1, // probabilityQ 1, // numCohorts 2) // numBloomHashes .encodeBits(toBytes(0b11111101L)))); assertEquals( 0xD56B8119L, toLong( new Encoder( makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 32, // numBits, 0, // probabilityF 0, // probabilityP 1, // probabilityQ 1, // numCohorts 2) // numBloomHashes .encodeBits(toBytes(0xD56B8119L)))); } @Test public void testEncoderEncodeBits_tooHigh() throws Exception { Encoder encoder = new Encoder(makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 8, // numBits, 0, // probabilityF 0, // probabilityP 1, // probabilityQ 1, // numCohorts 2); // numBloomHashes thrown.expect(IllegalArgumentException.class); encoder.encodeBits(toBytes(0x100)); // 9 bits } @Test public void testEncoderEncodeBoolean_identity() throws Exception { assertEquals( 0x1L, toLong( new Encoder( makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 1, // numBits, 0, // probabilityF 0, // probabilityP 1, // probabilityQ 1, // numCohorts 1) // numBloomHashes .encodeBoolean(true))); assertEquals( 0x0L, toLong( new Encoder( makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 1, // numBits, 0, // probabilityF 0, // probabilityP 1, // probabilityQ 1, // numCohorts 1) // numBloomHashes .encodeBoolean(false))); } @Test public void testEncoderEncodeOrdinal_identity() throws Exception { assertEquals( 0b000000000001L, toLong( new Encoder( makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 12, // numBits, 0, // probabilityF 0, // probabilityP 1, // probabilityQ 1, // numCohorts 1) // numBloomHashes .encodeOrdinal(0))); assertEquals( 0b100000000000L, toLong( new Encoder( makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 12, // numBits, 0, // probabilityF 0, // probabilityP 1, // probabilityQ 1, // numCohorts 1) // numBloomHashes .encodeOrdinal(11))); } @Test public void testEncoderEncodeOrdinal_tooLow() throws Exception { Encoder encoder = new Encoder(makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 12, // numBits, 0, // probabilityF 0, // probabilityP 1, // probabilityQ 1, // numCohorts 1); // numBloomHashes thrown.expect(IllegalArgumentException.class); encoder.encodeOrdinal(-1); } @Test public void testEncoderEncodeOrdinal_tooHigh() throws Exception { Encoder encoder = new Encoder(makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 12, // numBits, 0, // probabilityF 0, // probabilityP 1, // probabilityQ 1, // numCohorts 1); // numBloomHashes thrown.expect(IllegalArgumentException.class); encoder.encodeOrdinal(12); } @Test public void testEncoderEncodeString_identity() throws Exception { assertEquals( 0b000010000100L, toLong( new Encoder( makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 12, // numBits, 0, // probabilityF 0, // probabilityP 1, // probabilityQ 1, // numCohorts (so must be cohort 0) 2) // numBloomHashes .encodeString("Whizbang"))); // Changing the user but keeping the cohort the same (both cohort 0) // results in the same encoding. assertEquals( 0b000010000100L, toLong( new Encoder( makeTestingUserSecret("Blotto"), // userSecret "Foo", // encoderId 12, // numBits, 0, // probabilityF 0, // probabilityP 1, // probabilityQ 1, // numCohorts (so must be cohort 0) 2) // numBloomHashes .encodeString("Whizbang"))); // When the user is in a different cohort, she gets a different encoding. Encoder cohortProbeEncoder = new Encoder(makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 12, // numBits, 0, // probabilityF 0, // probabilityP 1, // probabilityQ 4, // numCohorts 2); // numBloomHashes assertEquals(3, cohortProbeEncoder.getCohort()); assertEquals(0b000011000000L, toLong(cohortProbeEncoder.encodeString("Whizbang"))); // Changing the string gets a different encoding. assertEquals( 0b001001000000L, toLong( new Encoder( makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 12, // numBits, 0, // probabilityF 0, // probabilityP 1, // probabilityQ 1, // numCohorts (so must be cohort 0) 2) // numBloomHashes .encodeString("Xyzzy"))); assertEquals( 0b000000110000L, toLong( new Encoder( makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 12, // numBits, 0, // probabilityF 0, // probabilityP 1, // probabilityQ 1, // numCohorts (so must be cohort 0) 2) // numBloomHashes .encodeString("Thud"))); } @Test public void testEncoderEncodeBits_prrMemoizes() throws Exception { assertEquals( 0b01110101L, toLong( new Encoder( makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 8, // numBits, 0.25, // probabilityF 0, // probabilityP 1, // probabilityQ 1, // numCohorts 2) // numBloomHashes .encodeBits(toBytes(0b11111101L)))); assertEquals( 0b11111101L, toLong( new Encoder( makeTestingUserSecret("Baz"), // userSecret "Foo", // encoderId 8, // numBits, 0.25, // probabilityF 0, // probabilityP 1, // probabilityQ 1, // numCohorts 2) // numBloomHashes .encodeBits(toBytes(0b11111101L)))); } @Test public void testEncoderEncodeBits_prrFlipProbability() throws Exception { int numSamples = 10000; int numBits = 8; double probabilityF = 1.0 / 32.0; long inputValue = 0b11111101L; int counts[] = new int[64]; for (int iSample = 0; iSample < numSamples; iSample++) { Encoder encoder = new Encoder(makeTestingUserSecret("User" + iSample), // userSecret "Foo", // encoderId numBits, // numBits, probabilityF, // probabilityF 0, // probabilityP 1, // probabilityQ 1, // numCohorts 2); // numBloomHashes long encoded = toLong(encoder.encodeBits(toBytes(inputValue))); assertEquals(encoded, toLong(encoder.encodeBits(toBytes(inputValue)))); for (int iBit = 0; iBit < numBits; iBit++) { if ((encoded & (1L << iBit)) != 0) { counts[iBit]++; } } } assertEquals(9843, counts[0]); // input = 1, expectation = 9843.75 assertEquals(173, counts[1]); // input = 0, expectation = 156.25 assertEquals(9839, counts[2]); // input = 1, expectation = 9843.75 assertEquals(9831, counts[3]); // input = 1, expectation = 9843.75 assertEquals(9848, counts[4]); // input = 1, expectation = 9843.75 assertEquals(9828, counts[5]); // input = 1, expectation = 9843.75 assertEquals(9834, counts[6]); // input = 1, expectation = 9843.75 assertEquals(9837, counts[7]); // input = 1, expectation = 9843.75 // Check that no high-order bit past numBits ever got set. for (int iBit = numBits; iBit < 64; iBit++) { assertEquals(0, counts[iBit]); } } @Test public void testEncoderEncodeBits_irrFlipProbability() throws Exception { int numBits = 8; double probabilityP = 0.25; double probabilityQ = 0.85; long inputValue = 0b11111101L; // BEGIN android-changed // SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); Random random = new Random(); // END android-changed random.setSeed(0x12345678L); int counts[] = new int[64]; for (int iSample = 0; iSample < 10000; iSample++) { Encoder encoder = new Encoder( random, null, // md5 null, // sha256 makeTestingUserSecret("User" + iSample), // userSecret "Foo", // encoderId numBits, // numBits, 0, // probabilityF probabilityP, // probabilityP probabilityQ, // probabilityQ 1, // numCohorts 2); // numBloomHashes long encoded = toLong(encoder.encodeBits(toBytes(inputValue))); for (int iBit = 0; iBit < numBits; iBit++) { if ((encoded & (1L << iBit)) != 0) { counts[iBit]++; } } } // BEGIN android-changed // assertEquals(8481, counts[0]); // input = 1, 99.99% CI = [8358, 8636] // assertEquals(2477, counts[1]); // input = 0, 99.99% CI = [2332, 2669] // assertEquals(8486, counts[2]); // input = 1, 99.99% CI = [8358, 8636] // assertEquals(8495, counts[3]); // input = 1, 99.99% CI = [8358, 8636] // assertEquals(8563, counts[4]); // input = 1, 99.99% CI = [8358, 8636] // assertEquals(8560, counts[5]); // input = 1, 99.99% CI = [8358, 8636] // assertEquals(8481, counts[6]); // input = 1, 99.99% CI = [8358, 8636] // assertEquals(8491, counts[7]); // input = 1, 99.99% CI = [8358, 8636] assertEquals(8492, counts[0]); // input = 1, 99.99% CI = [8358, 8636] assertEquals(2510, counts[1]); // input = 0, 99.99% CI = [2332, 2669] assertEquals(8476, counts[2]); // input = 1, 99.99% CI = [8358, 8636] assertEquals(8509, counts[3]); // input = 1, 99.99% CI = [8358, 8636] assertEquals(8406, counts[4]); // input = 1, 99.99% CI = [8358, 8636] assertEquals(8482, counts[5]); // input = 1, 99.99% CI = [8358, 8636] assertEquals(8498, counts[6]); // input = 1, 99.99% CI = [8358, 8636] assertEquals(8533, counts[7]); // input = 1, 99.99% CI = [8358, 8636] // END android-changed // Check that no high-order bit past numBits ever got set. for (int iBit = numBits; iBit < 64; iBit++) { assertEquals(0, counts[iBit]); } } @Test public void testEncoderEncodeBits_endToEnd() throws Exception { int numBits = 8; long inputValue = 0b11111101L; long prrValue = 0b01110101L; // BEGIN android-changed // long prrAndIrrValue = 0b01110110L; long prrAndIrrValue = 0b00111101L; // END android-changed // Verify that PRR is working as expected. assertEquals( prrValue, toLong( new Encoder( null, null, // md5 null, // sha256 makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId numBits, // numBits, 0.25, // probabilityF 0, // probabilityP 1, // probabilityQ 1, // numCohorts 2) // numBloomHashes .encodeBits(toBytes(inputValue)))); // Verify that IRR is working as expected. // BEGIN android-changed // SecureRandom random1 = SecureRandom.getInstance("SHA1PRNG"); Random random1 = new Random(); // END android-changed random1.setSeed(0x12345678L); assertEquals( prrAndIrrValue, toLong( new Encoder( random1, null, // md5 null, // sha256 makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId numBits, // numBits, 0, // probabilityF 0.3, // probabilityP 0.7, // probabilityQ 1, // numCohorts 2) // numBloomHashes .encodeBits(toBytes(prrValue)))); // Test that end-to-end is the result of PRR + IRR. // BEGIN android-changed // SecureRandom random2 = SecureRandom.getInstance("SHA1PRNG"); Random random2 = new Random(); // END android-changed random2.setSeed(0x12345678L); assertEquals( prrAndIrrValue, toLong( new Encoder( random2, null, // md5 null, // sha256 makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId numBits, // numBits, 0.25, // probabilityF 0.3, // probabilityP 0.7, // probabilityQ 1, // numCohorts 2) // numBloomHashes .encodeBits(toBytes(inputValue)))); } @Test public void testEncoderEncodeBits_32BitValuesEncodeSuccessfully() throws Exception { // Regression test for b/22035650. int numBits = 32; byte[] userSecret = makeTestingUserSecret("Bar"); // Explicitly spot-check the output for 2^0 and 2^31. long inputValue0 = 1L; // BEGIN android-changed // long outputValue0 = 590349342L; // SecureRandom random0 = SecureRandom.getInstance("SHA1PRNG"); long outputValue0 = 1117977L; Random random0 = new Random(); // END android-changed random0.setSeed(0x12345678L); assertEquals( outputValue0, toLong( new Encoder( random0, null, // md5 null, // sha256 userSecret, // userSecret "MyEncoder", // encoderId numBits, // numBits, 0.25, // probabilityF 0.3, // probabilityP 0.7, // probabilityQ 1, // numCohorts 2) // numBloomHashes .encodeBits(toBytes(inputValue0)))); long inputValue31 = 1L << 31; // BEGIN android-changed // long outputValue31 = 2746482838L; // SecureRandom random31 = SecureRandom.getInstance("SHA1PRNG"); long outputValue31 = 9505692L; Random random31 = new Random(); // END android-changed random31.setSeed(0x12345678L); assertEquals( outputValue31, toLong( new Encoder( random31, null, // md5 null, // sha256 userSecret, // userSecret "MyEncoder", // encoderId numBits, // numBits, 0.25, // probabilityF 0.3, // probabilityP 0.7, // probabilityQ 1, // numCohorts 2) // numBloomHashes .encodeBits(toBytes(inputValue31)))); // Check the range 2^1 to 2^30, making sure no values produce exceptions. // BEGIN android-changed // SecureRandom randomRange = SecureRandom.getInstance("SHA1PRNG"); Random randomRange = new Random(); // END android-changed randomRange.setSeed(0x12345678L); for (int i = 1; i <= 30; i++) { long inputValue = 1L << (i - 1); new Encoder( randomRange, null, // md5 null, // sha256 userSecret, // userSecret "MyEncoder", // encoderId numBits, // numBits, 0.25, // probabilityF 0.3, // probabilityP 0.7, // probabilityQ 1, // numCohorts 2) // numBloomHashes .encodeBits(toBytes(inputValue)); } } @Test public void testEncoderEncodeBits_63BitValuesEncodeSuccessfully() throws Exception { int numBits = 63; byte[] userSecret = makeTestingUserSecret("Bar"); // Explicitly spot-check the output for 2^0 and 2^63. long inputValue0 = 1L; // BEGIN android-changed // long outputValue0 = 867402030798341150L; // SecureRandom random0 = SecureRandom.getInstance("SHA1PRNG"); long outputValue0 = 5802301002133606169L; Random random0 = new Random(); // END android-changed random0.setSeed(0x12345678L); assertEquals( outputValue0, toLong( new Encoder( random0, null, // md5 null, // sha256 userSecret, // userSecret "MyEncoder", // encoderId numBits, // numBits, 0.25, // probabilityF 0.3, // probabilityP 0.7, // probabilityQ 1, // numCohorts 2) // numBloomHashes .encodeBits(toBytes(inputValue0)))); long inputValue63 = 1L << 62; // BEGIN android-changed // long outputValue63 = 5497102447743615126L; // SecureRandom random63 = SecureRandom.getInstance("SHA1PRNG"); long outputValue63 = 5874921546135456664L; Random random63 = new Random(); // END android-changed random63.setSeed(0x12345678L); assertEquals( outputValue63, toLong( new Encoder( random63, null, // md5 null, // sha256 userSecret, // userSecret "MyEncoder", // encoderId numBits, // numBits, 0.25, // probabilityF 0.3, // probabilityP 0.7, // probabilityQ 1, // numCohorts 2) // numBloomHashes .encodeBits(toBytes(inputValue63)))); // Check the range 2^1 to 2^62, making sure no values produce exceptions. // BEGIN android-changed // SecureRandom randomRange = SecureRandom.getInstance("SHA1PRNG"); Random randomRange = new Random(); // END android-changed randomRange.setSeed(0x12345678L); for (int i = 1; i <= 62; i++) { long inputValue = 1L << (i - 1); new Encoder( randomRange, null, // md5 null, // sha256 userSecret, // userSecret "MyEncoder", // encoderId numBits, // numBits, 0.25, // probabilityF 0.3, // probabilityP 0.7, // probabilityQ 1, // numCohorts 2) // numBloomHashes .encodeBits(toBytes(inputValue)); } } @Test public void testEncoderEncodeBits_4096BitValuesEncodeSuccessfully() throws Exception { int numBits = 4096; byte[] userSecret = makeTestingUserSecret("Bar"); // Check the range 2^1 to 2^4095, making sure no values produce exceptions. // BEGIN android-changed // SecureRandom randomRange = SecureRandom.getInstance("SHA1PRNG"); Random randomRange = new Random(); // END android-changed randomRange.setSeed(0x12345678L); // Stride is arbitrary, but chosen to be large enough to not cause too many probes (~40) and // prime to explore well. int stride = 97; for (int i = 1; i < numBits; i += stride) { new Encoder( randomRange, null, // md5 null, // sha256 userSecret, // userSecret "MyEncoder", // encoderId numBits, // numBits, 0.25, // probabilityF 0.3, // probabilityP 0.7, // probabilityQ 1, // numCohorts 2) // numBloomHashes .encodeOrdinal(i); } } @Test public void testGetEncoderId() throws Exception { Encoder encoder = new Encoder(makeTestingUserSecret("Bar"), // userSecret "Foo", // encoderId 8, // numBits, 13.0 / 128.0, // probabilityF 0.25, // probabilityP 0.75, // probabilityQ 1, // numCohorts 2); // numBloomHashes assertEquals("Foo", encoder.getEncoderId()); } } client/javatest/com/google/android/rappor/HmacDrbgTest.java 0100644 0000000 0000000 00000066751 13513561470 023057 0 ustar 00 0000000 0000000 package com.google.android.rappor; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import com.google.common.io.BaseEncoding; import com.google.common.primitives.Bytes; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Unit tests for {@link HmacDrbg}. * * Test vectors come from NIST's SHA-256 HMAC_DRBG no reseed known input test vectors at * http://csrc.nist.gov/groups/STM/cavp/random-number-generation.html#drbgvs */ @RunWith(JUnit4.class) public final class HmacDrbgTest { private byte[] hexToBytes(String s) { return BaseEncoding.base16().decode(s.toUpperCase()); } // ==== Test vectors for HMAC_DRBG with no personalization. ==== @Test public void testHmacDrbgNistCase0() { byte[] entropy = hexToBytes("ca851911349384bffe89de1cbdc46e6831e44d34a4fb935ee285dd14b71a7488"); byte[] nonce = hexToBytes("659ba96c601dc69fc902940805ec0ca8"); byte[] expected = hexToBytes( "e528e9abf2dece54d47c7e75e5fe302149f817ea9fb4bee6f4199697d04d5b89" + "d54fbb978a15b5c443c9ec21036d2460b6f73ebad0dc2aba6e624abf07745bc1" + "07694bb7547bb0995f70de25d6b29e2d3011bb19d27676c07162c8b5ccde0668" + "961df86803482cb37ed6d5c0bb8d50cf1f50d476aa0458bdaba806f48be9dcb8"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), null); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } @Test public void testHmacDrbgNistCase1() { byte[] entropy = hexToBytes("79737479ba4e7642a221fcfd1b820b134e9e3540a35bb48ffae29c20f5418ea3"); byte[] nonce = hexToBytes("3593259c092bef4129bc2c6c9e19f343"); byte[] expected = hexToBytes( "cf5ad5984f9e43917aa9087380dac46e410ddc8a7731859c84e9d0f31bd43655" + "b924159413e2293b17610f211e09f770f172b8fb693a35b85d3b9e5e63b1dc25" + "2ac0e115002e9bedfb4b5b6fd43f33b8e0eafb2d072e1a6fee1f159df9b51e6c" + "8da737e60d5032dd30544ec51558c6f080bdbdab1de8a939e961e06b5f1aca37"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), null); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } @Test public void testHmacDrbgNistCase2() { byte[] entropy = hexToBytes("b340907445b97a8b589264de4a17c0bea11bb53ad72f9f33297f05d2879d898d"); byte[] nonce = hexToBytes("65cb27735d83c0708f72684ea58f7ee5"); byte[] expected = hexToBytes( "75183aaaf3574bc68003352ad655d0e9ce9dd17552723b47fab0e84ef903694a" + "32987eeddbdc48efd24195dbdac8a46ba2d972f5808f23a869e71343140361f5" + "8b243e62722088fe10a98e43372d252b144e00c89c215a76a121734bdc485486" + "f65c0b16b8963524a3a70e6f38f169c12f6cbdd169dd48fe4421a235847a23ff"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), null); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } @Test public void testHmacDrbgNistCase3() { byte[] entropy = hexToBytes("8e159f60060a7d6a7e6fe7c9f769c30b98acb1240b25e7ee33f1da834c0858e7"); byte[] nonce = hexToBytes("c39d35052201bdcce4e127a04f04d644"); byte[] expected = hexToBytes( "62910a77213967ea93d6457e255af51fc79d49629af2fccd81840cdfbb491099" + "1f50a477cbd29edd8a47c4fec9d141f50dfde7c4d8fcab473eff3cc2ee9e7cc9" + "0871f180777a97841597b0dd7e779eff9784b9cc33689fd7d48c0dcd341515ac" + "8fecf5c55a6327aea8d58f97220b7462373e84e3b7417a57e80ce946d6120db5"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), null); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } @Test public void testHmacDrbgNistCase4() { byte[] entropy = hexToBytes("74755f196305f7fb6689b2fe6835dc1d81484fc481a6b8087f649a1952f4df6a"); byte[] nonce = hexToBytes("c36387a544a5f2b78007651a7b74b749"); byte[] expected = hexToBytes( "b2896f3af4375dab67e8062d82c1a005ef4ed119d13a9f18371b1b8737744186" + "84805fd659bfd69964f83a5cfe08667ddad672cafd16befffa9faed49865214f" + "703951b443e6dca22edb636f3308380144b9333de4bcb0735710e4d926678634" + "2fc53babe7bdbe3c01a3addb7f23c63ce2834729fabbd419b47beceb4a460236"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), null); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } @Test public void testHmacDrbgNistCase5() { byte[] entropy = hexToBytes("4b222718f56a3260b3c2625a4cf80950b7d6c1250f170bd5c28b118abdf23b2f"); byte[] nonce = hexToBytes("7aed52d0016fcaef0b6492bc40bbe0e9"); byte[] expected = hexToBytes( "a6da029b3665cd39fd50a54c553f99fed3626f4902ffe322dc51f0670dfe8742" + "ed48415cf04bbad5ed3b23b18b7892d170a7dcf3ef8052d5717cb0c1a8b3010d" + "9a9ea5de70ae5356249c0e098946030c46d9d3d209864539444374d8fbcae068" + "e1d6548fa59e6562e6b2d1acbda8da0318c23752ebc9be0c1c1c5b3cf66dd967"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), null); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } @Test public void testHmacDrbgNistCase6() { byte[] entropy = hexToBytes("b512633f27fb182a076917e39888ba3ff35d23c3742eb8f3c635a044163768e0"); byte[] nonce = hexToBytes("e2c39b84629a3de5c301db5643af1c21"); byte[] expected = hexToBytes( "fb931d0d0194a97b48d5d4c231fdad5c61aedf1c3a55ac24983ecbf38487b1c9" + "3396c6b86ff3920cfa8c77e0146de835ea5809676e702dee6a78100da9aa43d8" + "ec0bf5720befa71f82193205ac2ea403e8d7e0e6270b366dc4200be26afd9f63" + "b7e79286a35c688c57cbff55ac747d4c28bb80a2b2097b3b62ea439950d75dff"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), null); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } @Test public void testHmacDrbgNistCase7() { byte[] entropy = hexToBytes("aae3ffc8605a975befefcea0a7a286642bc3b95fb37bd0eb0585a4cabf8b3d1e"); byte[] nonce = hexToBytes("9504c3c0c4310c1c0746a036c91d9034"); byte[] expected = hexToBytes( "2819bd3b0d216dad59ddd6c354c4518153a2b04374b07c49e64a8e4d055575df" + "bc9a8fcde68bd257ff1ba5c6000564b46d6dd7ecd9c5d684fd757df62d852115" + "75d3562d7814008ab5c8bc00e7b5a649eae2318665b55d762de36eba00c2906c" + "0e0ec8706edb493e51ca5eb4b9f015dc932f262f52a86b11c41e9a6d5b3bd431"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), null); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } @Test public void testHmacDrbgNistCase8() { byte[] entropy = hexToBytes("b9475210b79b87180e746df704b3cbc7bf8424750e416a7fbb5ce3ef25a82cc6"); byte[] nonce = hexToBytes("24baf03599c10df6ef44065d715a93f7"); byte[] expected = hexToBytes( "ae12d784f796183c50db5a1a283aa35ed9a2b685dacea97c596ff8c294906d1b" + "1305ba1f80254eb062b874a8dfffa3378c809ab2869aa51a4e6a489692284a25" + "038908a347342175c38401193b8afc498077e10522bec5c70882b7f760ea5946" + "870bd9fc72961eedbe8bff4fd58c7cc1589bb4f369ed0d3bf26c5bbc62e0b2b2"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), null); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } @Test public void testHmacDrbgNistCase9() { byte[] entropy = hexToBytes("27838eb44ceccb4e36210703ebf38f659bc39dd3277cd76b7a9bcd6bc964b628"); byte[] nonce = hexToBytes("39cfe0210db2e7b0eb52a387476e7ea1"); byte[] expected = hexToBytes( "e5e72a53605d2aaa67832f97536445ab774dd9bff7f13a0d11fd27bf6593bfb5" + "2309f2d4f09d147192199ea584503181de87002f4ee085c7dc18bf32ce531564" + "7a3708e6f404d6588c92b2dda599c131aa350d18c747b33dc8eda15cf40e9526" + "3d1231e1b4b68f8d829f86054d49cfdb1b8d96ab0465110569c8583a424a099a"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), null); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } @Test public void testHmacDrbgNistCase10() { byte[] entropy = hexToBytes("d7129e4f47008ad60c9b5d081ff4ca8eb821a6e4deb91608bf4e2647835373a5"); byte[] nonce = hexToBytes("a72882773f78c2fc4878295840a53012"); byte[] expected = hexToBytes( "0cbf48585c5de9183b7ff76557f8fc9ebcfdfde07e588a8641156f61b7952725" + "bbee954f87e9b937513b16bba0f2e523d095114658e00f0f3772175acfcb3240" + "a01de631c19c5a834c94cc58d04a6837f0d2782fa53d2f9f65178ee9c8372224" + "94c799e64c60406069bd319549b889fa00a0032dd7ba5b1cc9edbf58de82bfcd"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), null); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } @Test public void testHmacDrbgNistCase11() { byte[] entropy = hexToBytes("67fe5e300c513371976c80de4b20d4473889c9f1214bce718bc32d1da3ab7532"); byte[] nonce = hexToBytes("e256d88497738a33923aa003a8d7845c"); byte[] expected = hexToBytes( "b44660d64ef7bcebc7a1ab71f8407a02285c7592d755ae6766059e894f694373" + "ed9c776c0cfc8594413eefb400ed427e158d687e28da3ecc205e0f7370fb0896" + "76bbb0fa591ec8d916c3d5f18a3eb4a417120705f3e2198154cd60648dbfcfc9" + "01242e15711cacd501b2c2826abe870ba32da785ed6f1fdc68f203d1ab43a64f"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), null); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } @Test public void testHmacDrbgNistCase12() { byte[] entropy = hexToBytes("de8142541255c46d66efc6173b0fe3ffaf5936c897a3ce2e9d5835616aafa2cb"); byte[] nonce = hexToBytes("d01f9002c407127bc3297a561d89b81d"); byte[] expected = hexToBytes( "64d1020929d74716446d8a4e17205d0756b5264867811aa24d0d0da8644db25d" + "5cde474143c57d12482f6bf0f31d10af9d1da4eb6d701bdd605a8db74fb4e77f" + "79aaa9e450afda50b18d19fae68f03db1d7b5f1738d2fdce9ad3ee9461b58ee2" + "42daf7a1d72c45c9213eca34e14810a9fca5208d5c56d8066bab1586f1513de7"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), null); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } @Test public void testHmacDrbgNistCase13() { byte[] entropy = hexToBytes("4a8e0bd90bdb12f7748ad5f147b115d7385bb1b06aee7d8b76136a25d779bcb7"); byte[] nonce = hexToBytes("7f3cce4af8c8ce3c45bdf23c6b181a00"); byte[] expected = hexToBytes( "320c7ca4bbeb7af977bc054f604b5086a3f237aa5501658112f3e7a33d2231f5" + "536d2c85c1dad9d9b0bf7f619c81be4854661626839c8c10ae7fdc0c0b571be3" + "4b58d66da553676167b00e7d8e49f416aacb2926c6eb2c66ec98bffae20864cf" + "92496db15e3b09e530b7b9648be8d3916b3c20a3a779bec7d66da63396849aaf"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), null); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } @Test public void testHmacDrbgNistCase14() { byte[] entropy = hexToBytes("451ed024bc4b95f1025b14ec3616f5e42e80824541dc795a2f07500f92adc665"); byte[] nonce = hexToBytes("2f28e6ee8de5879db1eccd58c994e5f0"); byte[] expected = hexToBytes( "3fb637085ab75f4e95655faae95885166a5fbb423bb03dbf0543be063bcd4879" + "9c4f05d4e522634d9275fe02e1edd920e26d9accd43709cb0d8f6e50aa54a5f3" + "bdd618be23cf73ef736ed0ef7524b0d14d5bef8c8aec1cf1ed3e1c38a808b35e" + "61a44078127c7cb3a8fd7addfa50fcf3ff3bc6d6bc355d5436fe9b71eb44f7fd"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), null); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } // ==== Test vectors for HMAC_DRBG with personalization. ==== @Test public void testHmacDrbgNistWithPersonalizationCase0() { byte[] entropy = hexToBytes("5cacc68165a2e2ee20812f35ec73a79dbf30fd475476ac0c44fc6174cdac2b55"); byte[] nonce = hexToBytes("6f885496c1e63af620becd9e71ecb824"); byte[] personalizationString = hexToBytes("e72dd8590d4ed5295515c35ed6199e9d211b8f069b3058caa6670b96ef1208d0"); byte[] expected = hexToBytes( "f1012cf543f94533df27fedfbf58e5b79a3dc517a9c402bdbfc9a0c0f721f9d5" + "3faf4aafdc4b8f7a1b580fcaa52338d4bd95f58966a243cdcd3f446ed4bc546d" + "9f607b190dd69954450d16cd0e2d6437067d8b44d19a6af7a7cfa8794e5fbd72" + "8e8fb2f2e8db5dd4ff1aa275f35886098e80ff844886060da8b1e7137846b23b"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), personalizationString); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } @Test public void testHmacDrbgNistWithPersonalizationCase1() { byte[] entropy = hexToBytes("8df013b4d103523073917ddf6a869793059e9943fc8654549e7ab22f7c29f122"); byte[] nonce = hexToBytes("da2625af2ddd4abcce3cf4fa4659d84e"); byte[] personalizationString = hexToBytes("b571e66d7c338bc07b76ad3757bb2f9452bf7e07437ae8581ce7bc7c3ac651a9"); byte[] expected = hexToBytes( "b91cba4cc84fa25df8610b81b641402768a2097234932e37d590b1154cbd23f9" + "7452e310e291c45146147f0da2d81761fe90fba64f94419c0f662b28c1ed94da" + "487bb7e73eec798fbcf981b791d1be4f177a8907aa3c401643a5b62b87b89d66" + "b3a60e40d4a8e4e9d82af6d2700e6f535cdb51f75c321729103741030ccc3a56"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), personalizationString); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } @Test public void testHmacDrbgNistWithPersonalizationCase2() { byte[] entropy = hexToBytes("565b2b77937ba46536b0f693b3d5e4a8a24563f9ef1f676e8b5b2ef17823832f"); byte[] nonce = hexToBytes("4ef3064ec29f5b7f9686d75a23d170e3"); byte[] personalizationString = hexToBytes("3b722433226c9dba745087270ab3af2c909425ba6d39f5ce46f07256068319d9"); byte[] expected = hexToBytes( "d144ee7f8363d128872f82c15663fe658413cd42651098e0a7c51a970de75287" + "ec943f9061e902280a5a9e183a7817a44222d198fbfab184881431b4adf35d3d" + "1019da5a90b3696b2349c8fba15a56d0f9d010a88e3f9eeedb67a69bcaa71281" + "b41afa11af576b765e66858f0eb2e4ec4081609ec81da81df0a0eb06787340ea"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), personalizationString); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } @Test public void testHmacDrbgNistWithPersonalizationCase3() { byte[] entropy = hexToBytes("fc3832a91b1dcdcaa944f2d93cbceb85c267c491b7b59d017cde4add79a836b6"); byte[] nonce = hexToBytes("d5e76ce9eabafed06e33a913e395c5e0"); byte[] personalizationString = hexToBytes("ffc5f6eefd51da64a0f67b5f0cf60d7ab43fc7836bca650022a0cee57a43c148"); byte[] expected = hexToBytes( "0e713c6cc9a4dbd4249201d12b7bf5c69c3e18eb504bf3252db2f43675e17d99" + "b6a908400cea304011c2e54166dae1f20260008efe4e06a87e0ce525ca482bca" + "223a902a14adcf2374a739a5dfeaf14cadd72efa4d55d15154c974d9521535bc" + "b70658c5b6c944020afb04a87b223b4b8e5d89821704a9985bb010405ba8f3d4"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), personalizationString); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } @Test public void testHmacDrbgNistWithPersonalizationCase4() { byte[] entropy = hexToBytes("8009eb2cb49fdf16403bcdfd4a9f952191062acb9cc111eca019f957fb9f4451"); byte[] nonce = hexToBytes("355598866952394b1eddd85d59f81c9d"); byte[] personalizationString = hexToBytes("09ff1d4b97d83b223d002e05f754be480d13ba968e5aac306d71cc9fc49cc2dd"); byte[] expected = hexToBytes( "9550903c2f02cf77c8f9c9a37041d0040ee1e3ef65ba1a1fbbcf44fb7a2172bd" + "6b3aaabe850281c3a1778277bacd09614dfefececac64338ae24a1bf150cbf9d" + "9541173a82ecba08aa19b75abb779eb10efa4257d5252e8afcac414bc3bb5d30" + "06b6f36fb9daea4c8c359ef6cdbeff27c1068571dd3c89dc87eda9190086888d"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), personalizationString); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } @Test public void testHmacDrbgNistWithPersonalizationCase5() { byte[] entropy = hexToBytes("a6e4c9a8bd6da23b9c2b10a7748fd08c4f782fadbac7ea501c17efdc6f6087bd"); byte[] nonce = hexToBytes("acdc47edf1d3b21d0aec7631abb6d7d5"); byte[] personalizationString = hexToBytes("c16ee0908a5886dccf332fbc61de9ec7b7972d2c4c83c477409ce8a15c623294"); byte[] expected = hexToBytes( "a52f93ccb363e2bdf0903622c3caedb7cffd04b726052b8d455744c71b76dee1" + "b71db9880dc3c21850489cb29e412d7d80849cfa9151a151dcbf32a32b4a54ca" + "c01d3200200ed66a3a5e5c131a49655ffbf1a8824ff7f265690dffb4054df46a" + "707b9213924c631c5bce379944c856c4f7846e281ac89c64fad3a49909dfb92b"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), personalizationString); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } @Test public void testHmacDrbgNistWithPersonalizationCase6() { byte[] entropy = hexToBytes("59d6307460a9bdd392dfc0904973991d585696010a71e52d590a5039b4849fa4"); byte[] nonce = hexToBytes("34a0aafb95917cbf8c38fc5548373c05"); byte[] personalizationString = hexToBytes("0407b7c57bc11361747c3d67526c36e228028a5d0b145d66ab9a2fe4b07507a0"); byte[] expected = hexToBytes( "299aba0661315211b09d2861855d0b4b125ab24649461341af6abd903ed6f025" + "223b3299f2126fcad44c675166d800619cf49540946b12138989417904324b0d" + "dad121327211a297f11259c9c34ce4c70c322a653675f78d385e4e2443f8058d" + "141195e17e0bd1b9d44bf3e48c376e6eb44ef020b11cf03eb141c46ecb43cf3d"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), personalizationString); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } @Test public void testHmacDrbgNistWithPersonalizationCase7() { byte[] entropy = hexToBytes("9ae3506aadbc8358696ba1ba17e876e1157b7048235921503d36d9211b430342"); byte[] nonce = hexToBytes("9abf7d66afee5d2b811cba358bbc527d"); byte[] personalizationString = hexToBytes("0d645f6238e9ceb038e4af9772426ca110c5be052f8673b8b5a65c4e53d2f519"); byte[] expected = hexToBytes( "5f032c7fec6320fe423b6f38085cbad59d826085afe915247b3d546c4c6b1745" + "54dd4877c0d671de9554b505393a44e71f209b70f991ac8aa6e08f983fff2a4c" + "817b0cd26c12b2c929378506489a75b2025b358cb5d0400821e7e252ac6376cd" + "94a40c911a7ed8b6087e3de5fa39fa6b314c3ba1c593b864ce4ff281a97c325b"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), personalizationString); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } @Test public void testHmacDrbgNistWithPersonalizationCase8() { byte[] entropy = hexToBytes("96ae3b8775b36da2a29b889ad878941f43c7d51295d47440cd0e3c4999193109"); byte[] nonce = hexToBytes("1fe022a6fc0237b055d4d6a7036b18d5"); byte[] personalizationString = hexToBytes("1e40e97362d0a823d3964c26b81ab53825c56446c5261689011886f19b08e5c2"); byte[] expected = hexToBytes( "e707cd14b06ce1e6dbcceaedbf08d88891b03f44ad6a797bd12fdeb557d0151d" + "f9346a028dec004844ca46adec3051dafb345895fa9f4604d8a13c8ff66ae093" + "fa63c4d9c0816d55a0066d31e8404c841e87b6b2c7b5ae9d7afb6840c2f7b441" + "bf2d3d8bd3f40349c1c014347c1979213c76103e0bece26ad7720601eff42275"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), personalizationString); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } @Test public void testHmacDrbgNistWithPersonalizationCase9() { byte[] entropy = hexToBytes("33f5120396336e51ee3b0b619b5f873db05ca57cda86aeae2964f51480d14992"); byte[] nonce = hexToBytes("6f1f6e9807ba5393edcf3cb4e4bb6113"); byte[] personalizationString = hexToBytes("3709605af44d90196867c927512aa8ba31837063337b4879408d91a05c8efa9f"); byte[] expected = hexToBytes( "8b8291126ded9acef12516025c99ccce225d844308b584b872c903c7bc646759" + "9a1cead003dc4c70f6d519f5b51ce0da57f53da90dbe8f666a1a1dde297727fe" + "e2d44cebd1301fc1ca75956a3fcae0d374e0df6009b668fd21638d2b733e6902" + "d22d5bfb4af1b455975e08eef0ebe4dc87705801e7776583c8de11672729f723"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), personalizationString); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } @Test public void testHmacDrbgNistWithPersonalizationCase10() { byte[] entropy = hexToBytes("ad300b799005f290fee7f930eebce158b98fb6cb449987fe433f955456b35300"); byte[] nonce = hexToBytes("06aa2514e4bd114edf7ac105cfef2772"); byte[] personalizationString = hexToBytes("87ada711465e4169da2a74c931afb9b5a5b190d07b7af342aa99570401c3ee8a"); byte[] expected = hexToBytes( "80d7c606ff49415a3a92ba1f2943235c01339c8f9cd0b0511fbfdf3ef23c42ff" + "ff008524193faaa4b7f2f2eb0cfa221d9df89bd373fe4e158ec06fad3ecf1eb4" + "8b8239b0bb826ee69d773883a3e8edac66254610ff70b6609836860e39ea1f3b" + "fa04596fee1f2baca6cebb244774c6c3eb4af1f02899eba8f4188f91776de16f"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), personalizationString); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } @Test public void testHmacDrbgNistWithPersonalizationCase11() { byte[] entropy = hexToBytes("130b044e2c15ab89375e54b72e7baae6d4cad734b013a090f4df057e634f6ff0"); byte[] nonce = hexToBytes("65fd6ac602cd44107d705dbc066e52b6"); byte[] personalizationString = hexToBytes("f374aba16f34d54aae5e494505b67d3818ef1c08ea24967a76876d4361379aec"); byte[] expected = hexToBytes( "5d179534fb0dba3526993ed8e27ec9f915183d967336bb24352c67f4ab5d7935" + "d3168e57008da851515efbaecb69904b6d899d3bfa6e9805659aef2942c49038" + "75b8fcbc0d1d24d1c075f0ff667c1fc240d8b410dff582fa71fa30878955ce2e" + "d786ef32ef852706e62439b69921f26e84e0f54f62b938f04905f05fcd7c2204"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), personalizationString); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } @Test public void testHmacDrbgNistWithPersonalizationCase12() { byte[] entropy = hexToBytes("716430e999964b35459c17921fe5f60e09bd9ab234cb8f4ba4932bec4a60a1d5"); byte[] nonce = hexToBytes("9533b711e061b07d505da707cafbca03"); byte[] personalizationString = hexToBytes("372ae616d1a1fc45c5aecad0939c49b9e01c93bfb40c835eebd837af747f079d"); byte[] expected = hexToBytes( "a80d6a1b2d0ce01fe0d26e70fb73da20d45841cf01bfbd50b90d2751a46114c0" + "e758cb787d281a0a9cf62f5c8ce2ee7ca74fefff330efe74926acca6d6f0646e" + "4e3c1a1e52fce1d57b88beda4a5815896f25f38a652cc240deb582921c8b1d03" + "a1da966dd04c2e7eee274df2cd1837096b9f7a0d89a82434076bc30173229a60"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), personalizationString); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } @Test public void testHmacDrbgNistWithPersonalizationCase13() { byte[] entropy = hexToBytes("7679f154296e6d580854826539003a82d1c54e2e062c619d00da6c6ac820789b"); byte[] nonce = hexToBytes("55d12941b0896462e7d888e5322a99a3"); byte[] personalizationString = hexToBytes("ba4d1ed696f58ef64596c76cee87cc1ca83069a79e7982b9a06f9d62f4209faf"); byte[] expected = hexToBytes( "10dc7cd2bb68c2c28f76d1b04ae2aa287071e04c3b688e1986b05cc1209f691d" + "aa55868ebb05b633c75a40a32b49663185fe5bb8f906008347ef51590530948b" + "87613920014802e5864e0758f012e1eae31f0c4c031ef823aecfb2f8a73aaa94" + "6fc507037f9050b277bdeaa023123f9d22da1606e82cb7e56de34bf009eccb46"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), personalizationString); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } @Test public void testHmacDrbgNistWithPersonalizationCase14() { byte[] entropy = hexToBytes("8ca4a964e1ff68753db86753d09222e09b888b500be46f2a3830afa9172a1d6d"); byte[] nonce = hexToBytes("a59394e0af764e2f21cf751f623ffa6c"); byte[] personalizationString = hexToBytes("eb8164b3bf6c1750a8de8528af16cffdf400856d82260acd5958894a98afeed5"); byte[] expected = hexToBytes( "fc5701b508f0264f4fdb88414768e1afb0a5b445400dcfdeddd0eba67b4fea8c" + "056d79a69fd050759fb3d626b29adb8438326fd583f1ba0475ce7707bd294ab0" + "1743d077605866425b1cbd0f6c7bba972b30fbe9fce0a719b044fcc139435489" + "5a9f8304a2b5101909808ddfdf66df6237142b6566588e4e1e8949b90c27fc1f"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), personalizationString); byte[] out1 = new byte[1024 / 8]; drbg.nextBytes(out1); byte[] out2 = new byte[1024 / 8]; drbg.nextBytes(out2); assertArrayEquals(expected, out2); } @Test public void testHmacDrbgGenerateEntropyInput() { byte[] got = HmacDrbg.generateEntropyInput(); assertEquals(got.length, HmacDrbg.ENTROPY_INPUT_SIZE_BYTES); } @Test public void testHmacDrbgZeroLengthOutput() { byte[] entropy = hexToBytes("8ca4a964e1ff68753db86753d09222e09b888b500be46f2a3830afa9172a1d6d"); byte[] nonce = hexToBytes("a59394e0af764e2f21cf751f623ffa6c"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), null); byte[] out = drbg.nextBytes(0); assertArrayEquals(new byte[0], out); } @Test public void testHmacDrbgCanGenerateMaxBytesOutput() { byte[] entropy = hexToBytes("8ca4a964e1ff68753db86753d09222e09b888b500be46f2a3830afa9172a1d6d"); byte[] nonce = hexToBytes("a59394e0af764e2f21cf751f623ffa6c"); HmacDrbg drbg = new HmacDrbg(Bytes.concat(entropy, nonce), null); byte[] out = drbg.nextBytes(HmacDrbg.MAX_BYTES_TOTAL); assertEquals(HmacDrbg.MAX_BYTES_TOTAL, out.length); } }