aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2018-08-06 16:50:42 -0700
committerXin Li <delphij@google.com>2018-08-06 16:50:42 -0700
commit61a1a6d194606fb275cf1c1c07a28f6cbc762072 (patch)
tree308f9979f2ea7bc08fed5ee43022c3ec51e75688
parent55a17365e016751e0f33f5d6b03e1d7ac67d4034 (diff)
parentcbcf969a038b3ed9eb5c297ec30bc0b62436ab03 (diff)
downloadrappor-61a1a6d194606fb275cf1c1c07a28f6cbc762072.tar.gz
Merge Android Pie into master
Bug: 112104996 Change-Id: I659ca9d324029c4b37ebdd3e483941af68c447ec
-rw-r--r--Android.bp26
-rw-r--r--Android.mk25
-rw-r--r--LICENSE202
-rw-r--r--MODULE_LICENSE_APACHE20
-rw-r--r--NOTICE202
-rw-r--r--README.android13
-rw-r--r--README.md38
-rw-r--r--README.version4
-rw-r--r--client/README.md43
-rw-r--r--client/java/com/google/android/rappor/Encoder.java613
-rw-r--r--client/java/com/google/android/rappor/HmacDrbg.java264
-rw-r--r--client/javatest/com/google/android/rappor/EncoderTest.java1044
-rw-r--r--client/javatest/com/google/android/rappor/HmacDrbgTest.java653
13 files changed, 3127 insertions, 0 deletions
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..db6d520
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,26 @@
+//
+// 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"],
+}
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..1fb2caf
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,25 @@
+#
+# 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)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+ 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.
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,202 @@
+
+ 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.
diff --git a/README.android b/README.android
new file mode 100644
index 0000000..30aae43
--- /dev/null
+++ b/README.android
@@ -0,0 +1,13 @@
+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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8527287
--- /dev/null
+++ b/README.md
@@ -0,0 +1,38 @@
+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
diff --git a/README.version b/README.version
new file mode 100644
index 0000000..19aa180
--- /dev/null
+++ b/README.version
@@ -0,0 +1,4 @@
+URL: https://github.com/google/rappor
+Version: a13fa964edb7c576366c83f40ff58d7c8c1db759
+BugComponent: 315013
+Owners: rickywai, pvisontay, simonjw
diff --git a/client/README.md b/client/README.md
new file mode 100644
index 0000000..bd79b89
--- /dev/null
+++ b/client/README.md
@@ -0,0 +1,43 @@
+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`.
+
+<!--
+
+TODO:
+- more details about protocol
+
+-->
+
+
+
+
+
+
+
+
diff --git a/client/java/com/google/android/rappor/Encoder.java b/client/java/com/google/android/rappor/Encoder.java
new file mode 100644
index 0000000..a8fb57c
--- /dev/null
+++ b/client/java/com/google/android/rappor/Encoder.java
@@ -0,0 +1,613 @@
+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.
+ *
+ * <p>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.
+ *
+ * <p>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.
+ *
+ * <p>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.
+ *
+ * <p>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].
+ *
+ * <p>This it the probability of replacing a bit from the input with a uniform random bit when
+ * generating the permanent randomized response.
+ *
+ * <p>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].
+ *
+ * <p>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'.
+ *
+ * <p>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].
+ *
+ * <p>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.
+ *
+ * <p>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.
+ *
+ * <p>Must satisfy 1 &lt;= numBits &lt;= MAX_BITS.
+ *
+ * <ul>
+ * <li>For encodeOrdinal, requires 0 &lt;= ordinal &lt; numBits.
+ * <li>For encodeString, uses a numBits-wide Bloom filter.
+ * <li>For encodeBits, only the low-order numBits may be non-zero.
+ * </ul>
+ */
+ private final int numBits;
+
+ /**
+ * The number of hash functions used forming the Bloom filter encoding of a string.
+ *
+ * <p>Must satisfy 1 &lt;= numBloomHashes &lt;= 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.
+ *
+ * <p>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.
+ *
+ * <p>The bitmask has the lowest order numBits set to 1 and the rest 0.
+ */
+ private final BitSet inputMask;
+
+ /**
+ * SHA-256 utility class instance.
+ *
+ * <p>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.
+ *
+ * <p>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.
+ *
+ * <p>The boolean is 0 or 1, then encoded using permanent and instantaneous randomized response.
+ *
+ * <p>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.
+ *
+ * <p>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.
+ *
+ * <p>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.
+ *
+ * <p>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.
+ *
+ * <p>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
+}
diff --git a/client/java/com/google/android/rappor/HmacDrbg.java b/client/java/com/google/android/rappor/HmacDrbg.java
new file mode 100644
index 0000000..db99700
--- /dev/null
+++ b/client/java/com/google/android/rappor/HmacDrbg.java
@@ -0,0 +1,264 @@
+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
+}
diff --git a/client/javatest/com/google/android/rappor/EncoderTest.java b/client/javatest/com/google/android/rappor/EncoderTest.java
new file mode 100644
index 0000000..896322f
--- /dev/null
+++ b/client/javatest/com/google/android/rappor/EncoderTest.java
@@ -0,0 +1,1044 @@
+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.
+ *
+ * <p>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());
+ }
+}
diff --git a/client/javatest/com/google/android/rappor/HmacDrbgTest.java b/client/javatest/com/google/android/rappor/HmacDrbgTest.java
new file mode 100644
index 0000000..e8b0f49
--- /dev/null
+++ b/client/javatest/com/google/android/rappor/HmacDrbgTest.java
@@ -0,0 +1,653 @@
+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);
+ }
+}