Automagic ACH Process

We’re trying to get ACH payments set up in Epicor.
I have it so that AP process the payments in Epicor and it generates a *.txt ACH file.
The requirement coming down to me, however, is that that’s not enough.
They expect, with a push of a button within Epicor, to create the file, encrypt it with PGP, and send it via SFTP to the bank.
Can this be done? Extreme customization?
Should I tell them they will have to encrypt and send it as a separate task?

Yes what you are wanting to do is possible. I wouldn’t say extreme customization, but it will require some. We do something similar for our ACH payments. Once we process them, we create a file and then send it over SFTP to our bank for them to authorize the check numbers.

@mathis1337 can you give me an overview of how you customized to achieve this result?

Sure, so we don’t do PGP here, but I can provide an example of PGP use in C#,
using System;

using System.IO;

using Org.BouncyCastle.Bcpg;

using Org.BouncyCastle.Bcpg.OpenPgp;

using Org.BouncyCastle.Security;

namespace Renaissance.Common.Encryption

{

/// <summary>

/// Wrapper around Bouncy Castle OpenPGP library.

/// Bouncy documentation can be found here: http://www.bouncycastle.org/docs/pgdocs1.6/index.html

/// </summary>

public class PgpEncrypt

{

    private PgpEncryptionKeys m_encryptionKeys;

    private const int BufferSize = 0x10000; // should always be power of 2 

    /// <summary>

    /// Instantiate a new PgpEncrypt class with initialized PgpEncryptionKeys.

    /// </summary>

    /// <param name="encryptionKeys"></param>

    /// <exception cref="ArgumentNullException">encryptionKeys is null</exception>

    public PgpEncrypt(PgpEncryptionKeys encryptionKeys)

    {

        if (encryptionKeys == null)

            throw new ArgumentNullException("encryptionKeys", "encryptionKeys is null.");

        m_encryptionKeys = encryptionKeys;

    }

    /// <summary>

    /// Encrypt and sign the file pointed to by unencryptedFileInfo and

    /// write the encrypted content to outputStream.

    /// </summary>

    /// <param name="outputStream">The stream that will contain the

    /// encrypted data when this method returns.</param>

    /// <param name="fileName">FileInfo of the file to encrypt</param>

    public void EncryptAndSign(Stream outputStream, FileInfo unencryptedFileInfo)

    {

        if (outputStream == null)

            throw new ArgumentNullException("outputStream", "outputStream is null.");

        if (unencryptedFileInfo == null)

            throw new ArgumentNullException("unencryptedFileInfo", "unencryptedFileInfo is null.");

        if (!File.Exists(unencryptedFileInfo.FullName))

            throw new ArgumentException("File to encrypt not found.");

        using (Stream encryptedOut = ChainEncryptedOut(outputStream))

        using (Stream compressedOut = ChainCompressedOut(encryptedOut))

        {

            PgpSignatureGenerator signatureGenerator = InitSignatureGenerator(compressedOut);

            using (Stream literalOut = ChainLiteralOut(compressedOut, unencryptedFileInfo))

            using (FileStream inputFile = unencryptedFileInfo.OpenRead())

            {

                WriteOutputAndSign(compressedOut, literalOut, inputFile, signatureGenerator);

            }

        }

    }

    private static void WriteOutputAndSign(Stream compressedOut,

        Stream literalOut,

        FileStream inputFile,

        PgpSignatureGenerator signatureGenerator)

    {

        int length = 0;

        byte[] buf = new byte[BufferSize];

        while ((length = inputFile.Read(buf, 0, buf.Length)) > 0)

        {

            literalOut.Write(buf, 0, length);

            signatureGenerator.Update(buf, 0, length);

        }

        signatureGenerator.Generate().Encode(compressedOut);

    }

    private Stream ChainEncryptedOut(Stream outputStream)

    {

        PgpEncryptedDataGenerator encryptedDataGenerator;

        encryptedDataGenerator =

            new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.TripleDes, 
                                          new SecureRandom());

        encryptedDataGenerator.AddMethod(m_encryptionKeys.PublicKey);

        return encryptedDataGenerator.Open(outputStream, new byte[BufferSize]);

    }

    private static Stream ChainCompressedOut(Stream encryptedOut)

    {

        PgpCompressedDataGenerator compressedDataGenerator =

            new PgpCompressedDataGenerator(CompressionAlgorithmTag.Zip);

        return compressedDataGenerator.Open(encryptedOut);

    }

    private static Stream ChainLiteralOut(Stream compressedOut, FileInfo file)

    {

        PgpLiteralDataGenerator pgpLiteralDataGenerator = new PgpLiteralDataGenerator();

        return pgpLiteralDataGenerator.Open(compressedOut, PgpLiteralData.Binary, file);

    }

    private PgpSignatureGenerator InitSignatureGenerator(Stream compressedOut)

    {

        const bool IsCritical = false;

        const bool IsNested = false;

        PublicKeyAlgorithmTag tag = m_encryptionKeys.SecretKey.PublicKey.Algorithm;

        PgpSignatureGenerator pgpSignatureGenerator =

            new PgpSignatureGenerator(tag, HashAlgorithmTag.Sha1);

        pgpSignatureGenerator.InitSign(PgpSignature.BinaryDocument, m_encryptionKeys.PrivateKey);

        foreach (string userId in m_encryptionKeys.SecretKey.PublicKey.GetUserIds())

        {

            PgpSignatureSubpacketGenerator subPacketGenerator = 
               new PgpSignatureSubpacketGenerator();

            subPacketGenerator.SetSignerUserId(IsCritical, userId);

            pgpSignatureGenerator.SetHashedSubpackets(subPacketGenerator.Generate());

            // Just the first one!

            break;

        }

        pgpSignatureGenerator.GenerateOnePassVersion(IsNested).Encode(compressedOut);

        return pgpSignatureGenerator;

    }

}

}

Now to upload the file to SFTP server you’d want to do something like this. This is using the Rebex SFTP component.
// create client, connect and log in
Sftp client = new Sftp();
client.Connect(hostname);
client.Login(username, password);

// upload the ‘test.zip’ file to the current directory at the server
client.PutFile(@“c:\data\test.zip”, “test.zip”);

client.Disconnect();

That is basically how it is done.

1 Like

That’s awesome. Thanks!
I’ll play around with this.

You are most welcome. You’ll need to import those libraries into the customzation to get them to work, but that is simple as downloading DLLs and you’re done :slight_smile:

Do you guys have any experience with modifying the Epicor ACH templates for these type of files?