google.registry.rde.BouncyCastleTest.java Source code

Java tutorial

Introduction

Here is the source code for google.registry.rde.BouncyCastleTest.java

Source

// Copyright 2016 The Nomulus Authors. All Rights Reserved.
//
// 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 google.registry.rde;

import static com.google.common.truth.Truth.assertThat;
import static google.registry.util.HexDumper.dumpHex;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.bouncycastle.bcpg.CompressionAlgorithmTags.ZIP;
import static org.bouncycastle.bcpg.HashAlgorithmTags.SHA256;
import static org.bouncycastle.bcpg.PublicKeyAlgorithmTags.RSA_GENERAL;
import static org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags.AES_128;

import com.google.common.io.CharStreams;
import google.registry.testing.BouncyCastleProviderRule;
import google.registry.util.FormattingLogger;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.Iterator;
import org.bouncycastle.openpgp.PGPCompressedData;
import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedDataList;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPOnePassSignature;
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.PGPSignatureList;
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.bc.BcPGPObjectFactory;
import org.bouncycastle.openpgp.bc.BcPGPPublicKeyRing;
import org.bouncycastle.openpgp.bc.BcPGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.bc.BcPGPSecretKeyRing;
import org.bouncycastle.openpgp.bc.BcPGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator;
import org.bouncycastle.util.encoders.Base64;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/**
 * Bouncy Castle – How does it work?!
 *
 * <p>I wrote these tests to teach myself how to use the Bouncy Castle cryptography library;
 * however I still believe these tests are useful enough to stick around in the domain registry
 * codebase because:
 *
 * <ol>
 * <li>They can serve as documentation for future maintainers about how Bouncy Castle works.
 * <li>They'll let us know if any of our assumptions about the library's behaviour are broken by
 *   future releases.
 * </ol>
 *
 * <p>Bouncy Castle is a very difficult library to use. OpenPGP (and cryptography in general!) is
 * complicated enough to begin with, but Bouncy Castle doesn't make life easier because it fails to
 * practice many of the design practices Java programmers take for granted, i.e. implementing
 * interfaces like {@link java.io.Closeable}. The only solid code examples that exist on the
 * Internet are the ones included with the library, and they don't not necessarily explain the
 * "right" way to do it. Much of what you see here I had to piece together myself by exploring the
 * Bouncy Castle code, taking tips here and there from our codebase and Stack Overflow responses.
 *
 * <p>The biggest threat we'll face in the future is that so many of the methods used in this file
 * are deprecated in the current (as of August 2013) Bouncy Castle release 1.49. We're still
 * using 1.46 so thankfully we're not far enough behind that the Bouncy Castle authors have decided
 * to remove these functions. But a migration effort will be necessary in the future.
 */
@RunWith(JUnit4.class)
@SuppressWarnings("resource")
public class BouncyCastleTest {

    private static final String FALL_OF_HYPERION_A_DREAM = "" + "Fanatics have their dreams, wherewith they weave\n"
            + "A paradise for a sect; the savage too\n" + "From forth the loftiest fashion of his sleep\n"
            + "Guesses at Heaven; pity these have not\n" + "Trac'd upon vellum or wild Indian leaf\n"
            + "The shadows of melodious utterance.\n" + "But bare of laurel they live, dream, and die;\n"
            + "For Poesy alone can tell her dreams,\n" + "With the fine spell of words alone can save\n"
            + "Imagination from the sable charm\n" + "And dumb enchantment. Who alive can say,\n"
            + "'Thou art no Poet may'st not tell thy dreams?'\n" + "Since every man whose soul is not a clod\n"
            + "Hath visions, and would speak, if he had loved\n" + "And been well nurtured in his mother tongue.\n"
            + "Whether the dream now purpos'd to rehearse\n" + "Be poet's or fanatic's will be known\n"
            + "When this warm scribe my hand is in the grave.\n";

    private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();

    @Rule
    public final BouncyCastleProviderRule bouncy = new BouncyCastleProviderRule();

    @Test
    public void testCompressDecompress() throws Exception {
        // Compress the data and write out a compressed data OpenPGP message.
        byte[] data;
        try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
            PGPCompressedDataGenerator kompressor = new PGPCompressedDataGenerator(ZIP);
            try (OutputStream output2 = kompressor.open(output)) {
                output2.write(FALL_OF_HYPERION_A_DREAM.getBytes(UTF_8));
            }
            data = output.toByteArray();
        }
        logger.info("Compressed data: " + dumpHex(data));

        // Decompress the data.
        try (ByteArrayInputStream input = new ByteArrayInputStream(data)) {
            PGPObjectFactory pgpFact = new BcPGPObjectFactory(input);
            PGPCompressedData object = (PGPCompressedData) pgpFact.nextObject();
            InputStream original = object.getDataStream(); // Closing this would close input.
            assertThat(CharStreams.toString(new InputStreamReader(original, UTF_8)))
                    .isEqualTo(FALL_OF_HYPERION_A_DREAM);
            assertThat(pgpFact.nextObject()).isNull();
        }
    }

    @Test
    public void testSignVerify_Detached() throws Exception {
        // Load the keys.
        PGPPublicKeyRing publicKeyRing = new BcPGPPublicKeyRing(PUBLIC_KEY);
        PGPSecretKeyRing privateKeyRing = new BcPGPSecretKeyRing(PRIVATE_KEY);
        PGPPublicKey publicKey = publicKeyRing.getPublicKey();
        PGPPrivateKey privateKey = extractPrivateKey(privateKeyRing.getSecretKey());

        // Sign the data and write signature data to "signatureFile".
        // Note: RSA_GENERAL will encrypt AND sign. RSA_SIGN and RSA_ENCRYPT are deprecated.
        PGPSignatureGenerator signer = new PGPSignatureGenerator(
                new BcPGPContentSignerBuilder(RSA_GENERAL, SHA256));
        signer.init(PGPSignature.BINARY_DOCUMENT, privateKey);
        addUserInfoToSignature(publicKey, signer);
        signer.update(FALL_OF_HYPERION_A_DREAM.getBytes(UTF_8));
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        signer.generate().encode(output);
        byte[] signatureFileData = output.toByteArray();
        logger.info(".sig file data: " + dumpHex(signatureFileData));

        // Load algorithm information and signature data from "signatureFileData".
        PGPSignature sig;
        try (ByteArrayInputStream input = new ByteArrayInputStream(signatureFileData)) {
            PGPObjectFactory pgpFact = new BcPGPObjectFactory(input);
            PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject();
            assertThat(sigList.size()).isEqualTo(1);
            sig = sigList.get(0);
        }

        // Use "onePass" and "sig" to verify "publicKey" signed the text.
        sig.init(new BcPGPContentVerifierBuilderProvider(), publicKey);
        sig.update(FALL_OF_HYPERION_A_DREAM.getBytes(UTF_8));
        assertThat(sig.verify()).isTrue();

        // Verify that they DIDN'T sign the text "hello monster".
        sig.init(new BcPGPContentVerifierBuilderProvider(), publicKey);
        sig.update("hello monster".getBytes(UTF_8));
        assertThat(sig.verify()).isFalse();
    }

    @Test
    public void testSignVerify_OnePass() throws Exception {
        // Load the keys.
        PGPPublicKeyRing publicKeyRing = new BcPGPPublicKeyRing(PUBLIC_KEY);
        PGPSecretKeyRing privateKeyRing = new BcPGPSecretKeyRing(PRIVATE_KEY);
        PGPPublicKey publicKey = publicKeyRing.getPublicKey();
        PGPPrivateKey privateKey = extractPrivateKey(privateKeyRing.getSecretKey());

        // Sign the data and write signature data to "signatureFile".
        PGPSignatureGenerator signer = new PGPSignatureGenerator(
                new BcPGPContentSignerBuilder(RSA_GENERAL, SHA256));
        signer.init(PGPSignature.BINARY_DOCUMENT, privateKey);
        addUserInfoToSignature(publicKey, signer);
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        signer.generateOnePassVersion(false).encode(output);
        signer.update(FALL_OF_HYPERION_A_DREAM.getBytes(UTF_8));
        signer.generate().encode(output);
        byte[] signatureFileData = output.toByteArray();
        logger.info(".sig file data: " + dumpHex(signatureFileData));

        // Load algorithm information and signature data from "signatureFileData".
        PGPSignature sig;
        PGPOnePassSignature onePass;
        try (ByteArrayInputStream input = new ByteArrayInputStream(signatureFileData)) {
            PGPObjectFactory pgpFact = new BcPGPObjectFactory(input);
            PGPOnePassSignatureList onePassList = (PGPOnePassSignatureList) pgpFact.nextObject();
            PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject();
            assertThat(onePassList.size()).isEqualTo(1);
            assertThat(sigList.size()).isEqualTo(1);
            onePass = onePassList.get(0);
            sig = sigList.get(0);
        }

        // Use "onePass" and "sig" to verify "publicKey" signed the text.
        onePass.init(new BcPGPContentVerifierBuilderProvider(), publicKey);
        onePass.update(FALL_OF_HYPERION_A_DREAM.getBytes(UTF_8));
        assertThat(onePass.verify(sig)).isTrue();

        // Verify that they DIDN'T sign the text "hello monster".
        onePass.init(new BcPGPContentVerifierBuilderProvider(), publicKey);
        onePass.update("hello monster".getBytes(UTF_8));
        assertThat(onePass.verify(sig)).isFalse();
    }

    @Test
    public void testEncryptDecrypt_ExplicitStyle() throws Exception {
        int bufferSize = 64 * 1024;

        // Alice loads Bob's "publicKey" into memory.
        PGPPublicKeyRing publicKeyRing = new BcPGPPublicKeyRing(PUBLIC_KEY);
        PGPPublicKey publicKey = publicKeyRing.getPublicKey();

        // Alice encrypts the secret message for Bob using his "publicKey".
        PGPEncryptedDataGenerator encryptor = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(AES_128));
        encryptor.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(publicKey));
        byte[] encryptedData;
        try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
            try (OutputStream output2 = encryptor.open(output, new byte[bufferSize])) {
                output2.write(FALL_OF_HYPERION_A_DREAM.getBytes(UTF_8));
            }
            encryptedData = output.toByteArray();
        }
        logger.info("Encrypted data: " + dumpHex(encryptedData));

        // Bob loads his "privateKey" into memory.
        PGPSecretKeyRing privateKeyRing = new BcPGPSecretKeyRing(PRIVATE_KEY);
        PGPPrivateKey privateKey = extractPrivateKey(privateKeyRing.getSecretKey());

        // Bob decrypt's the OpenPGP message (w/ ciphertext) using his "privateKey".
        try (ByteArrayInputStream input = new ByteArrayInputStream(encryptedData)) {
            PGPObjectFactory pgpFact = new BcPGPObjectFactory(input);
            PGPEncryptedDataList encDataList = (PGPEncryptedDataList) pgpFact.nextObject();
            assertThat(encDataList.size()).isEqualTo(1);
            PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) encDataList.get(0);
            assertThat(encData.getKeyID()).isEqualTo(publicKey.getKeyID());
            assertThat(encData.getKeyID()).isEqualTo(privateKey.getKeyID());
            try (InputStream original = encData.getDataStream(new BcPublicKeyDataDecryptorFactory(privateKey))) {
                assertThat(CharStreams.toString(new InputStreamReader(original, UTF_8)))
                        .isEqualTo(FALL_OF_HYPERION_A_DREAM);
            }
        }
    }

    @Test
    public void testEncryptDecrypt_KeyRingStyle() throws Exception {
        int bufferSize = 64 * 1024;

        // Alice loads Bob's "publicKey" into memory from her public key ring.
        PGPPublicKeyRingCollection publicKeyRings = new BcPGPPublicKeyRingCollection(
                PGPUtil.getDecoderStream(new ByteArrayInputStream(PUBLIC_KEY)));
        PGPPublicKeyRing publicKeyRing = publicKeyRings.getKeyRings("eric@bouncycastle.org", true, true).next();
        PGPPublicKey publicKey = publicKeyRing.getPublicKey();

        // Alice encrypts the secret message for Bob using his "publicKey".
        PGPEncryptedDataGenerator encryptor = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(AES_128));
        encryptor.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(publicKey));
        byte[] encryptedData;
        try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
            try (OutputStream output2 = encryptor.open(output, new byte[bufferSize])) {
                output2.write(FALL_OF_HYPERION_A_DREAM.getBytes(UTF_8));
            }
            encryptedData = output.toByteArray();
        }
        logger.info("Encrypted data: " + dumpHex(encryptedData));

        // Bob loads his chain of private keys into memory.
        PGPSecretKeyRingCollection privateKeyRings = new BcPGPSecretKeyRingCollection(
                PGPUtil.getDecoderStream(new ByteArrayInputStream(PRIVATE_KEY)));

        // Bob decrypt's the OpenPGP message (w/ ciphertext) using his "privateKey".
        try (ByteArrayInputStream input = new ByteArrayInputStream(encryptedData)) {
            PGPObjectFactory pgpFact = new BcPGPObjectFactory(input);
            PGPEncryptedDataList encDataList = (PGPEncryptedDataList) pgpFact.nextObject();
            assertThat(encDataList.size()).isEqualTo(1);
            PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) encDataList.get(0);
            // Bob loads the private key to which the message is addressed.
            PGPPrivateKey privateKey = extractPrivateKey(privateKeyRings.getSecretKey(encData.getKeyID()));
            try (InputStream original = encData.getDataStream(new BcPublicKeyDataDecryptorFactory(privateKey))) {
                assertThat(CharStreams.toString(new InputStreamReader(original, UTF_8)))
                        .isEqualTo(FALL_OF_HYPERION_A_DREAM);
            }
        }
    }

    @Test
    public void testCompressEncryptDecryptDecompress_KeyRingStyle() throws Exception {
        int bufsz = 64 * 1024;

        // Alice loads Bob's "publicKey" into memory from her public key ring.
        PGPPublicKeyRingCollection publicKeyRings = new BcPGPPublicKeyRingCollection(
                PGPUtil.getDecoderStream(new ByteArrayInputStream(PUBLIC_KEY)));
        PGPPublicKeyRing publicKeyRing = publicKeyRings.getKeyRings("eric@bouncycastle.org", true, true).next();
        PGPPublicKey publicKey = publicKeyRing.getPublicKey();

        // Alice encrypts the secret message for Bob using his "publicKey".
        PGPEncryptedDataGenerator encryptor = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(AES_128));
        encryptor.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(publicKey));
        byte[] encryptedData;
        try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
            try (OutputStream output2 = encryptor.open(output, new byte[bufsz])) {
                PGPCompressedDataGenerator kompressor = new PGPCompressedDataGenerator(ZIP);
                try (OutputStream output3 = kompressor.open(output2, new byte[bufsz])) {
                    output3.write(FALL_OF_HYPERION_A_DREAM.getBytes(UTF_8));
                }
            }
            encryptedData = output.toByteArray();
        }
        logger.info("Encrypted data: " + dumpHex(encryptedData));

        // Bob loads his chain of private keys into memory.
        PGPSecretKeyRingCollection privateKeyRings = new BcPGPSecretKeyRingCollection(
                PGPUtil.getDecoderStream(new ByteArrayInputStream(PRIVATE_KEY)));

        // Bob decrypt's the OpenPGP message (w/ ciphertext) using his "privateKey".
        try (ByteArrayInputStream input = new ByteArrayInputStream(encryptedData)) {
            PGPObjectFactory pgpFact = new BcPGPObjectFactory(input);
            PGPEncryptedDataList encDataList = (PGPEncryptedDataList) pgpFact.nextObject();
            assertThat(encDataList.size()).isEqualTo(1);
            PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) encDataList.get(0);
            // Bob loads the private key to which the message is addressed.
            PGPPrivateKey privateKey = extractPrivateKey(privateKeyRings.getSecretKey(encData.getKeyID()));
            try (InputStream original = encData.getDataStream(new BcPublicKeyDataDecryptorFactory(privateKey))) {
                pgpFact = new BcPGPObjectFactory(original);
                PGPCompressedData kompressedData = (PGPCompressedData) pgpFact.nextObject();
                try (InputStream orig2 = kompressedData.getDataStream()) {
                    assertThat(CharStreams.toString(new InputStreamReader(orig2, UTF_8)))
                            .isEqualTo(FALL_OF_HYPERION_A_DREAM);
                }
            }
        }
    }

    /**
     * Add user ID to signature file.
     *
     * <p>This adds information about the identity of the signer to the signature file. It's not
     * required, but I'm guessing it could be a lifesaver if somewhere down the road, people lose
     * track of the public keys and need to figure out how to verify a couple blobs. This would at
     * least tell them which key to download from the MIT keyserver.
     *
     * <p>But the main reason why I'm using this is because I copied it from the code of another
     * Googler who was also uncertain about the precise reason why it's needed.
     */
    private void addUserInfoToSignature(PGPPublicKey publicKey, PGPSignatureGenerator signer) {
        @SuppressWarnings("unchecked") // Safe by specification.
        Iterator<String> uidIter = publicKey.getUserIDs();
        if (uidIter.hasNext()) {
            PGPSignatureSubpacketGenerator spg = new PGPSignatureSubpacketGenerator();
            spg.setSignerUserID(false, uidIter.next());
            signer.setHashedSubpackets(spg.generate());
        }
    }

    private static PGPPrivateKey extractPrivateKey(PGPSecretKey secretKey) throws PGPException {
        return secretKey.extractPrivateKey(
                new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(PASSWORD));
    }

    private static final char[] PASSWORD = "hello world".toCharArray();

    private static final byte[] PUBLIC_KEY = Base64
            .decode("" + "mIsEPz2nJAEEAOTVqWMvqYE693qTgzKv/TJpIj3hI8LlYPC6m1dk0z3bDLwVVk9F"
                    + "FAB+CWS8RdFOWt/FG3tEv2nzcoNdRvjv9WALyIGNawtae4Ml6oAT06/511yUzXHO"
                    + "k+9xK3wkXN5jdzUhf4cA2oGpLSV/pZlocsIDL+jCUQtumUPwFodmSHhzAAYptC9F"
                    + "cmljIEVjaGlkbmEgKHRlc3Qga2V5KSA8ZXJpY0Bib3VuY3ljYXN0bGUub3JnPoi4"
                    + "BBMBAgAiBQI/PackAhsDBQkAg9YABAsHAwIDFQIDAxYCAQIeAQIXgAAKCRA1WGFG"
                    + "/fPzc8WMA/9BbjuB8E48QAlxoiVf9U8SfNelrz/ONJA/bMvWr/JnOGA9PPmFD5Uc"
                    + "+kV/q+i94dEMjsC5CQ1moUHWSP2xlQhbOzBP2+oPXw3z2fBs9XJgnTH6QWMAAvLs"
                    + "3ug9po0loNHLobT/D/XdXvcrb3wvwvPT2FptZqrtonH/OdzT9JdfrA==");

    private static final byte[] PRIVATE_KEY = Base64
            .decode("" + "lQH8BD89pyQBBADk1aljL6mBOvd6k4Myr/0yaSI94SPC5WDwuptXZNM92wy8FVZP"
                    + "RRQAfglkvEXRTlrfxRt7RL9p83KDXUb47/VgC8iBjWsLWnuDJeqAE9Ov+ddclM1x"
                    + "zpPvcSt8JFzeY3c1IX+HANqBqS0lf6WZaHLCAy/owlELbplD8BaHZkh4cwAGKf4D"
                    + "AwKbLeIOVYTEdWD5v/YgW8ERs0pDsSIfBTvsJp2qA798KeFuED6jGsHUzdi1M990"
                    + "6PRtplQgnoYmYQrzEc6DXAiAtBR4Kuxi4XHx0ZR2wpVlVxm2Ypgz7pbBNWcWqzvw"
                    + "33inl7tR4IDsRdJOY8cFlN+1tSCf16sDidtKXUVjRjZNYJytH18VfSPlGXMeYgtw"
                    + "3cSGNTERwKaq5E/SozT2MKTiORO0g0Mtyz+9MEB6XVXFavMun/mXURqbZN/k9BFb"
                    + "z+TadpkihrLD1xw3Hp+tpe4CwPQ2GdWKI9KNo5gEnbkJgLrSMGgWalPhknlNHRyY"
                    + "bSq6lbIMJEE3LoOwvYWwweR1+GrV9farJESdunl1mDr5/d6rKru+FFDwZM3na1IF"
                    + "4Ei4FpqhivZ4zG6pN5XqLy+AK85EiW4XH0yAKX1O4YlbmDU4BjxhiwTdwuVMCjLO"
                    + "5++jkz5BBQWdFX8CCMA4FJl36G70IbGzuFfOj07ly7QvRXJpYyBFY2hpZG5hICh0"
                    + "ZXN0IGtleSkgPGVyaWNAYm91bmN5Y2FzdGxlLm9yZz6IuAQTAQIAIgUCPz2nJAIb"
                    + "AwUJAIPWAAQLBwMCAxUCAwMWAgECHgECF4AACgkQNVhhRv3z83PFjAP/QW47gfBO"
                    + "PEAJcaIlX/VPEnzXpa8/zjSQP2zL1q/yZzhgPTz5hQ+VHPpFf6voveHRDI7AuQkN"
                    + "ZqFB1kj9sZUIWzswT9vqD18N89nwbPVyYJ0x+kFjAALy7N7oPaaNJaDRy6G0/w/1"
                    + "3V73K298L8Lz09habWaq7aJx/znc0/SXX6w=");
}