1

I have created a trigger and a trigger function which invokes on every update operation on a table and encrypts a specific value as below:

create trigger project_trigger
before update
on projects
for each row
execute procedure project_function();

create or replace function project_function()
returns trigger as
$BODY$
begin
IF (TG_OP = 'UPDATE') THEN
NEW.title = armor(pgp_sym_encrypt(NEW.title, 'cipher-algo=aes256' ));
return NEW;
END IF;
end;
$BODY$
language plpgsql;

The above approach for encryption is working fine and an armored PGP encrypted value gets saved as below:

-----BEGIN PGP MESSAGE-----

ww0EBwMCneBsNZw1gYFq0jYB9y58EoTaRXWmDFqvQArWU5tZ+wS+7yAm9ycVUpkH1EzvYLbfRoDj
rqR83I0nGErHcLSLlAs=
=IYg8
-----END PGP MESSAGE-----

Decryption needs to be done at the application level for which I followed the following 2 steps:

  • Added bcpg-jdk15on and bcprov-jdk15on dependencies. (v1.47)
  • Implementation:
String key = "aes_key";
File file = new File("D:\\file.txt.asc"); //this file contains the PGP encrypted value as shown above
InputStream input = new FileInputStream(file);
byte[] byt = new byte[input.available()];
input.read(byt);
input.close();
Security.addProvider(new BouncyCastleProvider());
System.out.println(new String(ByteArrayHandler.decrypt(byt, 
                    key.toCharArray())));

I keep getting the following exception while using the above approach to decrypt the value:

Exception in thread "main" org.bouncycastle.openpgp.PGPDataValidationException: data check failed. at org.bouncycastle.openpgp.PGPPBEEncryptedData.getDataStream(Unknown Source) at org.bouncycastle.openpgp.examples.ByteArrayHandler.decrypt(Unknown Source) at abc.demo.encryption.SymmetricDecyption.main(SymmetricDecyption.java:59)

So can someone guide me to the appropriate approach to achieve decryption at the application level (not in the queries).

Gadhia Reema
  • 186
  • 3
  • 17
  • It makes no sense to use the value itself as a password, because you need the password to decrypt the data. – Laurenz Albe Mar 08 '21 at 08:13
  • @LaurenzAlbe please check now, that was actually the encryption key – Gadhia Reema Mar 08 '21 at 08:24
  • I am talking about the PostgreSQL function. You are calling `pgp_sym_encrypt_bytea` with the string that is to be encrypted as password. Think again. – Laurenz Albe Mar 08 '21 at 08:28
  • @LaurenzAlbe I will configure the encryptionKey in a conf file or read it from the db in the next phase, this is just a poc and decryption is not working as expected – Gadhia Reema Mar 08 '21 at 08:43
  • The encryption is working fine, but you cannot decrypt the message without knowing the password. If you store the password in the database, you might as well store the data unencrypted, because the encryption offers no protection whatsoever. I think you should get some basic education about encryption. – Laurenz Albe Mar 08 '21 at 08:46
  • Your problem seems to be entirely with java, and not with postgresql or pgcrypto at all. Although your choice of 'cipher-algo=aes256' as a password is distinctly odd. – jjanes Mar 08 '21 at 15:51
  • Bouncy Castle has really working **examples** that show how encryption and decryption with PGP is working: https://github.com/bcgit/bc-java/tree/master/pg/src/main/java/org/bouncycastle/openpgp/examples. Btw: your chosen BC version "1.47" is **21** version too old, the actual version is 1.68 :-) – Michael Fehr Mar 09 '21 at 10:56
  • @MichaelFehr I have used ByteArrayHandler example from the github link you shared for decryption as mentioned in the question above. I updated BC version to 1.68 as per your suggestion still getting the "PGPDataValidationException: data check failed." exception – Gadhia Reema Mar 10 '21 at 10:13
  • Here are a lot of friendly people that try to help you - are you being able to share a complete **sample** dataset (private & public pgp key, password for the key if used, plaintext, password, ciphertext)? Without these information its difficult to find a solution, thanks. – Michael Fehr Mar 10 '21 at 11:35

1 Answers1

2

There are two problems. The PGPDataValidationException is caused by using a different pass-phrase for encryption and decryption. If you had used the correct pass-phrase, then you would have found that the Bouncy Castle example code is not fully functional.

The trigger is probably not what you intended. The call to pgp_sym_encrypt should look more like this:

create or replace function project_function()
returns trigger as
$BODY$
begin
IF (TG_OP = 'UPDATE') THEN
NEW.title = armor(pgp_sym_encrypt(NEW.title, 'my-secret-passphrase', 'cipher-algo=aes256, compress-algo=2' ));
return NEW;
END IF;
end;
$BODY$
language plpgsql;

The three input parameters to pgp_sym_encrypt are the text to be encrypted, the pass phrase from which the cipher key will be derived, and options. In your question, you omitted the pass phrase.

Second the BouncyCastle example code assumes that the plain text has been compressed. I have added RFC1950 compression (ZLIB) to the pgp_sym_encrypt.

With those changes to the trigger I get:

postgres=# update projects set title = 'My secret compressed title.';
UPDATE 1
postgres=# \t off
postgres=# select * from projects;
title
-----BEGIN PGP MESSAGE-----

ww0ECQMCuN3MyfrWhBt50lcBGbUtjOlTBxGpAFCl7aYEybhhXRJodDsikWxdLmOsXnE6vWr9mwd7
dGy7N1eE5VFmwI5N29eCNhEvG5U4YmVC7fV1A1sBeoJMtsO/nz2mi2jbFiZHlzo=
=s6uI
-----END PGP MESSAGE-----

(1 row)
postgres=#

Feeding that into a Java program:

    String value = "-----BEGIN PGP MESSAGE-----\n"
        + "\n"
        + "ww0ECQMCuN3MyfrWhBt50lcBGbUtjOlTBxGpAFCl7aYEybhhXRJodDsikWxdLmOsXnE6vWr9mwd7\n"
        + "dGy7N1eE5VFmwI5N29eCNhEvG5U4YmVC7fV1A1sBeoJMtsO/nz2mi2jbFiZHlzo=\n"
        + "=s6uI\n"
        + "-----END PGP MESSAGE-----\n";

    String key = "my-secret-passphrase";
    byte[] byt = value.getBytes(StandardCharsets.UTF_8);
    Security.addProvider(new BouncyCastleProvider());
    System.out.println(new String(ByteArrayHandler.decrypt(byt, key.toCharArray()), StandardCharsets.UTF_8));

Produces the output:

My secret compressed title.

Exactly as desired.

If you want to not compress the plain text before encrypting it, then you can look at the example PBEFileProcessor as this handles both compressed and uncompressed data, or you can just use this code:

  public static byte[] decrypt(
      byte[] encrypted,
      char[] passPhrase
  ) throws IOException, PGPException {
    JcaPGPObjectFactory pgpF = new JcaPGPObjectFactory(PGPUtil.getDecoderStream(new ByteArrayInputStream(encrypted)));

    // Find the encrypted data list. The first object might be a PGP marker packet, or the actual data list
    PGPEncryptedDataList enc;
    Object o = pgpF.nextObject();
    if (o instanceof PGPEncryptedDataList) {
      enc = (PGPEncryptedDataList) o;
    } else {
      enc = (PGPEncryptedDataList) pgpF.nextObject();
    }

    // Do the decryption
    PGPPBEEncryptedData pbe = (PGPPBEEncryptedData) enc.get(0);
    InputStream clear = pbe.getDataStream(new JcePBEDataDecryptorFactoryBuilder(
        new JcaPGPDigestCalculatorProviderBuilder().setProvider("BC").build()).setProvider("BC").build(passPhrase)
    );

    // Process the decrypted data. It may be compressed, or it may be literal
    JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(clear);
    o = pgpFact.nextObject();
    if (o instanceof PGPCompressedData) {
      // Need to decompress the data
      PGPCompressedData cData = (PGPCompressedData) o;
      pgpFact = new JcaPGPObjectFactory(cData.getDataStream());
      o = pgpFact.nextObject();
    }

    // We should have the literal data now, so convert it into bytes
    PGPLiteralData ld = (PGPLiteralData) o;
    return Streams.readAll(ld.getInputStream());
  }

Finally, when decrypting in postgresql you do not need to specify whether the plain text was compressed, nor how it was encrypted, as the PGP data specifies this, so you can do:

select pgp_sym_decrypt(dearmor(title), 'my-secret-passphrase') from projects;
Simon G.
  • 6,587
  • 25
  • 30
  • Thankyou Simon, on updating the trigger functions and code, decryption worked as expected. I probably missed the compress-algo param and setting the appropriate Charset. – Gadhia Reema Mar 11 '21 at 11:22