csharp

Digital Signature

Install package

dotnet add package itext
dotnet add package itext.bouncy-castle-adapter

SignPdf

Create certificate

RSA rsa = RSA.Create();
CertificateRequest request = new CertificateRequest("CN=ceb.net.vn", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
X509Certificate2 certificate2 = request.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddYears(1));
byte[] bytes = certificate2.Export(X509ContentType.Pfx, "123456");
File.WriteAllBytes("certificate.pfx", bytes);

SignPdf

public static byte[] SignPdf(byte[] pdfBytes){
    PdfFont font = PdfFontFactory.CreateFont(@"C:\Windows\Fonts\arial.ttf", PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED);
    string path = Path.Combine(Directory.GetCurrentDirectory(), "certificates", "certificate.pfx");
    string password = "123456";
    using MemoryStream inputMs = new MemoryStream(pdfBytes);
    using MemoryStream outputMs = new MemoryStream();
    Pkcs12Store pkcs12Store = new Pkcs12StoreBuilder().Build();
    using(FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read)){
        pkcs12Store.Load(fs, password.ToArray());
    }
    string alias = null!;
    foreach(string a in pkcs12Store.Aliases){
        if (pkcs12Store.IsKeyEntry(a)){
            alias = a;
            break;
        }
    }
    var bcPrivateKey = pkcs12Store.GetKey(alias).Key;
    var privateKey = new PrivateKeyBC(bcPrivateKey);
    var certificateChain = pkcs12Store.GetCertificateChain(alias).Select(p => new X509CertificateBC(p.Certificate)).ToArray();
    var signerProperties = new SignerProperties()
        .SetFieldName("cebnetvn")
        .SetReason("Ký hóa đơn điện tử")
        .SetLocation("Thành Phố Hồ Chí Minh")
        .SetPageNumber(1)
        .SetPageRect(new iText.Kernel.Geom.Rectangle(207, 70, 180, 70));
    var appearance = new SignatureFieldAppearance("cebnetvn")
        .SetContent($"Đã Đăng Ký Số\nNguyễn Văn Tèo\nNgày ký: {DateTime.Now:dd/MM/yyyy HH:mm}").SetFont(font).SetFontSize(12);
    signerProperties.SetSignatureAppearance(appearance);
    var reader = new PdfReader(new MemoryStream(pdfBytes));
    var signer = new PdfSigner(reader,  outputMs, new StampingProperties());
    signer.SetSignerProperties(signerProperties);
    IExternalSignature signature = new PrivateKeySignature(privateKey, DigestAlgorithms.SHA256);
    signer.SignDetached(signature, certificateChain, null, null, null, 0, PdfSigner.CryptoStandard.CADES);
    return outputMs.ToArray();
}

Signature Result

public class SignatureResult{
    public string Name { get; set; } = null!;
    public bool IsValid { get; set; }
    public DateTime SigningDate { get; set; }
}

Verify Pdf

public static IEnumerable<SignatureResult> Verify(MemoryStream ms){
    try{
        ms.Position = 0;
        using var reader = new PdfReader(ms);
        using var pdf = new PdfDocument(reader);
        var signatureUtil = new SignatureUtil(pdf);
        var names = signatureUtil.GetSignatureNames();
        if(names is null || names.Count == 0){
            throw new Exception("PDf not found digital signature");
        }
        var results = new List<SignatureResult>();
        foreach (var name in names){
            var pkcs7 = signatureUtil.ReadSignatureData(name);
            bool valid = pkcs7.VerifySignatureIntegrityAndAuthenticity();
            results.Add(new SignatureResult{
                Name = name,
                IsValid = valid,
                SigningDate = pkcs7.GetSignDate()
            });
        }
        return results;
    }
    catch (Exception ex){
        throw new Exception(ex.Message);
        }
    }

Export Pdf

public static byte[] ExportPdf(Invoice invoice, IEnumerable<Product> products){
    PdfFont font = PdfFontFactory.CreateFont(@"C:\Windows\Fonts\arial.ttf", PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED);
    MemoryStream pdfStream = new MemoryStream();
    PdfWriter writer = new PdfWriter(pdfStream);
    Document document = new Document(new PdfDocument(writer));
    document.SetMargins(40, 40, 40, 40);
    Paragraph title = new Paragraph("HÓA ĐƠN BÁN HÀNG")
        .SetTextAlignment(TextAlignment.CENTER).SetFont(font).SetFontSize(20);
    document.Add(title);
    document.Add(new Paragraph($"Số hóa đơn: {invoice.Id}\nNgay2: {invoice.InvoiceDate:dd/MM/yyyy HH:mm}").SetFont(font).SetFontSize(12));
    document.Add(new Paragraph($"Khách hàng: {invoice.CustomerName}\nĐịa chỉ: {invoice.CustomerAddress}").SetFont(font).SetFontSize(12));
    document.Add(new Paragraph(" "));
    var table = new Table(UnitValue.CreatePercentArray(new float[]{1, 4, 1, 2, 2}));
    table.SetWidth(UnitValue.CreatePercentValue(100));
    string[] columns = {"STT", "Sản phẩm", "SL", "Đơn giá", "Thành tiền"};
    foreach (string col in columns){
        table.AddHeaderCell(col).SetFont(font).SetFontSize(12);
    }
    int srNo = 1;
    foreach (var product in products){
        decimal amount = product.Quantity * product.Price;
        table.AddCell(srNo.ToString());
        table.AddCell(product.Name);
        table.AddCell(product.Quantity.ToString());
        table.AddCell(product.Price.ToString("#,##0", CultureInfo.InvariantCulture));
        table.AddCell(amount.ToString("#,##0", CultureInfo.InvariantCulture));
        srNo ++;
    }
    document.Add(table);
    document.Add(new Paragraph(" "));
    decimal total = products.Sum(p => p.Quantity * p.Price);
    document.Add(new Paragraph($"Tổng cộng: {total:#,##0} VNĐ")).SetTextAlignment(TextAlignment.RIGHT).SetFont(font).SetFontSize(14);
    document.Add(new Paragraph(" "));
    document.Add(new Paragraph(" "));
    var signTable = new Table(UnitValue.CreatePercentArray(new float[]{1, 1}));
    signTable.SetWidth(UnitValue.CreatePercentValue(100));
    var footer = new Paragraph("NGƯỜI MUA HÀNG\n\n\n\n(Ký, ghi rõ họ tên)").SetFont(font).SetTextAlignment(TextAlignment.CENTER);
    signTable.AddCell(new Cell().SetBorder(Border.NO_BORDER).Add(footer));
    Paragraph footCustomer = new Paragraph("NGƯỜI BÁN HÀNG\n\n\n\n(Đã ký số)").SetFont(font).SetTextAlignment(TextAlignment.CENTER);
    signTable.AddCell(new Cell().SetBorder(Border.NO_BORDER).Add(footCustomer));
    document.Add(signTable);
    document.Close();
    var signedPdf = Helper.SignPdf(pdfStream.ToArray());
    return signedPdf.ToArray();
}

Subject

Ký hiệu Tên Ví dụ
`CN` Common Name `ceb.net.vn`
`O` Organization `CEB Company`
`OU` Organizational Unit `IT Department`
`L` Locality `Ho Chi Minh City`
`ST` State/Province `Ho Chi Minh`
`C` Country `VN`
`STREET` Địa chỉ đường `123 Nguyen Hue`
`DC` Domain Component `net`, `vn`
`UID` User ID `user123`
`SERIALNUMBER` Số định danh `0123456789`
`E` / `emailAddress`Email `admin@ceb.net.vn`

Create Subject

var builder = new X500DistinguishedNameBuilder();
builder.AddCommonName("CEB");
builder.AddOrganizationName("Tin Học Cho Mọi Người");
builder.AddLocalityName("Thành phố Hồ Chí Minh");
builder.AddStateOrProvinceName("Hồ Chí Minh");
builder.AddCountryOrRegion("VN");
var subject = builder.Build();