Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0d1bc332 authored by Ricky Wai's avatar Ricky Wai
Browse files

Add differential privacy library and algorithms

- Created a differential privacy framework interface
- Added 2 DP algorithms in DP framework:
  -  Rappor, a wrapper based on external/rappor project
  -  Longitudinal Reporting, DP enhancement based on Rappor
- Created Privacy Tests for testing all privacy libraries
- Added original Rappor test case in privacy test
- Created tests to verify Rappor and Longitudinal Reporting result in DP framework

Test: bit FrameworksPrivacyLibraryTests:android.privacy.LongitudinalReportingEncoderTest
Test: bit FrameworksPrivacyLibraryTests:android.privacy.RapporEncoderTest

Change-Id: Id460665059653924434c141686b5cad3fb697046
parent b430d8ff
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -671,6 +671,7 @@ java_library {
        "libphonenumber-platform",
        "nist-sip",
        "tagsoup",
        "rappor",
    ],
    dxflags: ["--core-library"],
}
+34 −0
Original line number Diff line number Diff line
/*
 * Copyright 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.
 */

package android.privacy;

/**
 * An interface for differential privacy configuration.
 * {@link DifferentialPrivacyEncoder} will apply this configuration to do differential privacy
 * encoding.
 *
 * @hide
 */
public interface DifferentialPrivacyConfig {

    /**
     * Returns the name of the algorithm used in differential privacy config.
     *
     * @return The name of the algorithm
     */
    String getAlgorithm();
}
+78 −0
Original line number Diff line number Diff line
/*
 * Copyright 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.
 */

package android.privacy;

/**
 * An interface for differential privacy encoder.
 * Applications can use it to convert privacy sensitive data to privacy protected report.
 * There is no decoder implemented in Android as it is not possible decode a single report by
 * design.
 *
 * <p>Each type of log should have its own encoder, otherwise it may leak
 * some information about Permanent Randomized Response(PRR, is used to create a “noisy”
 * answer which is memoized by the client and permanently reused in place of the real answer).
 *
 * <p>Some encoders may not support all encoding methods, and it will throw {@link
 * UnsupportedOperationException} if you call unsupported encoding method.
 *
 * <p><b>WARNING:</b> Privacy protection works only when encoder uses a suitable DP configuration,
 * and the configuration and algorithm that is suitable is highly dependent on the use case.
 * If the configuration is not suitable for the use case, it may hurt privacy or utility or both.
 *
 * @hide
 */
public interface DifferentialPrivacyEncoder {

    /**
     * Apply differential privacy to encode a string.
     *
     * @param original An arbitrary string
     * @return Differential privacy encoded bytes derived from the string
     */
    byte[] encodeString(String original);

    /**
     * Apply differential privacy to encode a boolean.
     *
     * @param original An arbitrary boolean.
     * @return Differential privacy encoded bytes derived from the boolean
     */
    byte[] encodeBoolean(boolean original);

    /**
     * Apply differential privacy to encode sequence of bytes.
     *
     * @param original An arbitrary byte array.
     * @return Differential privacy encoded bytes derived from the bytes
     */
    byte[] encodeBits(byte[] original);

    /**
     * Returns the configuration that this encoder is using.
     */
    DifferentialPrivacyConfig getConfig();

    /**
     * Return True if the output from encoder is NOT securely randomized, otherwise encoder should
     * be secure to randomize input.
     *
     * <b> A non-secure encoder is intended only for testing only and must not be used to process
     * real data.
     * </b>
     */
    boolean isInsecureEncoderForTest();
}
+107 −0
Original line number Diff line number Diff line
/*
 * Copyright 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.
 */

package android.privacy.internal.longitudinalreporting;

import android.privacy.DifferentialPrivacyConfig;
import android.privacy.internal.rappor.RapporConfig;
import android.text.TextUtils;

import com.android.internal.util.Preconditions;

/**
 * A class to store {@link LongitudinalReportingEncoder} configuration.
 *
 * <ul>
 * <li> f is probability to flip input value, used in IRR.
 * <li> p is probability to override input value, used in PRR1.
 * <li> q is probability to set input value as 1 when result of PRR(p) is true, used in PRR2.
 * </ul>
 *
 * @hide
 */
public class LongitudinalReportingConfig implements DifferentialPrivacyConfig {

    private static final String ALGORITHM_NAME = "LongitudinalReporting";

    // Probability to flip input value.
    private final double mProbabilityF;

    // Probability to override original value.
    private final double mProbabilityP;
    // Probability to override value with 1.
    private final double mProbabilityQ;

    // IRR config to randomize original value
    private final RapporConfig mIRRConfig;

    private final String mEncoderId;

    /**
     * Constructor to create {@link LongitudinalReportingConfig} used for {@link
     * LongitudinalReportingEncoder}
     *
     * @param encoderId    Unique encoder id.
     * @param probabilityF Probability F used in Longitudinal Reporting algorithm.
     * @param probabilityP Probability P used in Longitudinal Reporting algorithm. This will be
     *                     quantized to the nearest 1/256.
     * @param probabilityQ Probability Q used in Longitudinal Reporting algorithm. This will be
     *                     quantized to the nearest 1/256.
     */
    public LongitudinalReportingConfig(String encoderId, double probabilityF,
            double probabilityP, double probabilityQ) {
        Preconditions.checkArgument(probabilityF >= 0 && probabilityF <= 1,
                "probabilityF must be in range [0.0, 1.0]");
        this.mProbabilityF = probabilityF;
        Preconditions.checkArgument(probabilityP >= 0 && probabilityP <= 1,
                "probabilityP must be in range [0.0, 1.0]");
        this.mProbabilityP = probabilityP;
        Preconditions.checkArgument(probabilityQ >= 0 && probabilityQ <= 1,
                "probabilityQ must be in range [0.0, 1.0]");
        this.mProbabilityQ = probabilityQ;
        Preconditions.checkArgument(!TextUtils.isEmpty(encoderId), "encoderId cannot be empty");
        mEncoderId = encoderId;
        mIRRConfig = new RapporConfig(encoderId, 1, 0.0, probabilityF, 1 - probabilityF, 1, 1);
    }

    @Override
    public String getAlgorithm() {
        return ALGORITHM_NAME;
    }

    RapporConfig getIRRConfig() {
        return mIRRConfig;
    }

    double getProbabilityP() {
        return mProbabilityP;
    }

    double getProbabilityQ() {
        return mProbabilityQ;
    }

    String getEncoderId() {
        return mEncoderId;
    }

    @Override
    public String toString() {
        return String.format("EncoderId: %s, ProbabilityF: %.3f, ProbabilityP: %.3f"
                        + ", ProbabilityQ: %.3f",
                mEncoderId, mProbabilityF, mProbabilityP, mProbabilityQ);
    }
}
+170 −0
Original line number Diff line number Diff line
/*
 * Copyright 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.
 */

package android.privacy.internal.longitudinalreporting;

import android.privacy.DifferentialPrivacyEncoder;
import android.privacy.internal.rappor.RapporConfig;
import android.privacy.internal.rappor.RapporEncoder;

import com.android.internal.annotations.VisibleForTesting;

/**
 * Differential privacy encoder by using Longitudinal Reporting algorithm.
 *
 * <b>
 * Notes: It supports encodeBoolean() only for now.
 * </b>
 *
 * <p>
 * Definition:
 * PRR = Permanent Randomized Response
 * IRR = Instantaneous Randomized response
 *
 * Algorithm:
 * Step 1: Create long-term secrets x(ignoreOriginalInput)=Ber(P), y=Ber(Q), where Ber denotes
 * Bernoulli distribution on {0, 1}, and we use it as a long-term secret, we implement Ber(x) by
 * using PRR(2x, 0) when x < 1/2, PRR(2(1-x), 1) when x >= 1/2.
 * Step 2: If x is 0, report IRR(F, original), otherwise report IRR(F, y)
 * </p>
 *
 * Reference: go/bit-reporting-with-longitudinal-privacy
 * TODO: Add a public blog / site to explain how it works.
 *
 * @hide
 */
public class LongitudinalReportingEncoder implements DifferentialPrivacyEncoder {

    // Suffix that will be added to Rappor's encoder id. There's a (relatively) small risk some
    // other Rappor encoder may re-use the same encoder id.
    private static final String PRR1_ENCODER_ID = "prr1_encoder_id";
    private static final String PRR2_ENCODER_ID = "prr2_encoder_id";

    private final LongitudinalReportingConfig mConfig;

    // IRR encoder to encode input value.
    private final RapporEncoder mIRREncoder;

    // A value that used to replace original value as input, so there's always a chance we are
    // doing IRR on a fake value not actual original value.
    // Null if original value does not need to be replaced.
    private final Boolean mFakeValue;

    // True if encoder is securely randomized.
    private final boolean mIsSecure;

    /**
     * Create {@link LongitudinalReportingEncoder} with
     * {@link LongitudinalReportingConfig} provided.
     *
     * @param config     Longitudinal Reporting parameters to encode input
     * @param userSecret User generated secret that used to generate PRR
     * @return {@link LongitudinalReportingEncoder} instance
     */
    public static LongitudinalReportingEncoder createEncoder(LongitudinalReportingConfig config,
            byte[] userSecret) {
        return new LongitudinalReportingEncoder(config, true, userSecret);
    }

    /**
     * Create <strong>insecure</strong> {@link LongitudinalReportingEncoder} with
     * {@link LongitudinalReportingConfig} provided.
     * Should not use it to process sensitive data.
     *
     * @param config Rappor parameters to encode input.
     * @return {@link LongitudinalReportingEncoder} instance.
     */
    @VisibleForTesting
    public static LongitudinalReportingEncoder createInsecureEncoderForTest(
            LongitudinalReportingConfig config) {
        return new LongitudinalReportingEncoder(config, false, null);
    }

    private LongitudinalReportingEncoder(LongitudinalReportingConfig config,
            boolean secureEncoder, byte[] userSecret) {
        mConfig = config;
        mIsSecure = secureEncoder;
        final boolean ignoreOriginalInput = getLongTermRandomizedResult(config.getProbabilityP(),
                secureEncoder, userSecret, config.getEncoderId() + PRR1_ENCODER_ID);

        if (ignoreOriginalInput) {
            mFakeValue = getLongTermRandomizedResult(config.getProbabilityQ(),
                    secureEncoder, userSecret, config.getEncoderId() + PRR2_ENCODER_ID);
        } else {
            // Not using fake value, so IRR will be processed on real input value.
            mFakeValue = null;
        }

        final RapporConfig irrConfig = config.getIRRConfig();
        mIRREncoder = secureEncoder
                ? RapporEncoder.createEncoder(irrConfig, userSecret)
                : RapporEncoder.createInsecureEncoderForTest(irrConfig);
    }

    @Override
    public byte[] encodeString(String original) {
        throw new UnsupportedOperationException();
    }

    @Override
    public byte[] encodeBoolean(boolean original) {
        if (mFakeValue != null) {
            // Use the fake value generated in PRR.
            original = mFakeValue.booleanValue();
        }
        return mIRREncoder.encodeBoolean(original);
    }

    @Override
    public byte[] encodeBits(byte[] bits) {
        throw new UnsupportedOperationException();
    }

    @Override
    public LongitudinalReportingConfig getConfig() {
        return mConfig;
    }

    @Override
    public boolean isInsecureEncoderForTest() {
        return !mIsSecure;
    }

    /**
     * Get PRR result that with probability p is 1, probability 1-p is 0.
     */
    @VisibleForTesting
    public static boolean getLongTermRandomizedResult(double p, boolean secureEncoder,
            byte[] userSecret, String encoderId) {
        // Use Rappor to get PRR result. Rappor's P and Q are set to 0 and 1 so IRR will not be
        // effective.
        // As Rappor has rapporF/2 chance returns 0, rapporF/2 chance returns 1, and 1-rapporF
        // chance returns original input.
        // If p < 0.5, setting rapporF=2p and input=0 will make Rappor has p chance to return 1
        // P(output=1 | input=0) = rapporF/2 = 2p/2 = p.
        // If p >= 0.5, setting rapporF=2(1-p) and input=1 will make Rappor has p chance
        // to return 1.
        // P(output=1 | input=1) = rapporF/2 + (1 - rapporF) = 2(1-p)/2 + (1 - 2(1-p)) = p.
        final double effectiveF = p < 0.5f ? p * 2 : (1 - p) * 2;
        final boolean prrInput = p < 0.5f ? false : true;
        final RapporConfig prrConfig = new RapporConfig(encoderId, 1, effectiveF,
                0, 1, 1, 1);
        final RapporEncoder encoder = secureEncoder
                ? RapporEncoder.createEncoder(prrConfig, userSecret)
                : RapporEncoder.createInsecureEncoderForTest(prrConfig);
        return encoder.encodeBoolean(prrInput)[0] > 0;
    }
}
Loading