Bir raporu her şeye izin vermeyi amaçlayan bir maskeyle — $FFFFFFF0 — şifrelersiniz ve Acrobat'ın Security sekmesi, belgeyi yazdırmayı ve değiştirmeyi izin verilmedi olarak listeler. /P, 1. bittten itibaren numaralanmış işaretsiz bir 32-bit bayrak sözcüğüdür; o maske, print (bit 3) ile modify'ı (bit 4) temizler. Her şeye izin veren değer $FFFFFFFC'dir — PDFium Component içindeki TPdfEncryptOptions'ın başlangıç noktası
/P bayrak sözcüğü nasıl dizilir
Yalnızca sekiz konum anlam taşır; kalanı saklıdır ve sayma 1'den başlar. ISO 32000-1 §7.6.3.2, P'nin "işaretsiz bir 32-bit nicelik olarak yorumlanması gerektiğini" söyler; düşük sıra ucunda 1'den yüksek sıra ucunda 32'ye numaralanır ve kurallara uyan okuyuculara, 3, 4, 5, 6, 9, 10, 11 ve 12 dışındaki her biti yok saymalarını söyler. ISO 32000-2 §7.6.4.2, Table 22'yi düzeltir ve saklı konumları açıkça yazar: 1–2 bitleri 0 olmalı, 7–8 bitleri 1 olmalı ve revision 3 ile sonrasında 13–32 bitleri 1 olmalıdır. PDF 2.0 ayrıca 10. biti emekliye ayırır; okuyucular artık onu yok sayar, yazıcılar eski okuyucular için her zaman 1 yazar. Erişilebilirlik ayrıcalığı 5. bite taşınır; böylece içerik yardımcı teknolojiye gidiyorsa okuyucu, kopyalama izinliymiş gibi davranmalıdır. Tek bir formül, aşağıdaki her satırı kapsar. Bit n'in maskesi 1 shl (n - 1)'dir — TPdfPermission da aynı şekilde sözcüğe oturur
| Bit | Maske | Anlam (revision 3 ve sonrası) | TPdfPermission |
|---|---|---|---|
| 1–2 | $00000003 | Saklı, 0 olmalı | yok |
| 3 | $00000004 | Yazdırma; kalitesi 12. bite bağlıdır | permPrint |
| 4 | $00000008 | İçeriği, 6, 9 ve 11. bitlerin işlemleri dışındaki işlemlerle değiştirme | permModify |
| 5 | $00000010 | Metni ve grafikleri kopyalama ya da çıkarma | permCopy |
| 6 | $00000020 | Annotation ekleme ya da değiştirme, alan doldurma; bit 4 ile birlikte alan oluşturma | permAnnotate |
| 7–8 | $000000C0 | Saklı, 1 olmalı | yok |
| 9 | $00000100 | Mevcut form alanlarını — signature alanları dahil — 6. bit temiz olsa bile doldurma | permFillForms |
| 10 | $00000200 | Erişilebilirlik çıkarma; PDF 2.0'da kullanılmıyor, her zaman 1 yazılır | permAccessibility |
| 11 | $00000400 | Assemble: sayfa ekleme, döndürme ya da silme; outline ögeleri ve thumbnail oluşturma | permAssemble |
| 12 | $00000800 | Sadık, yüksek kaliteli bir temsile yazdırma | permPrintHighQuality |
| 13–32 | $FFFFF000 | Saklı, 1 olmalı (ISO/TS 32004, 13. biti kendine alır; aşağıda) | yok |
$FFFFFFF0 maskesi, "alt bitler saklıdır" okumasından ve güvence olsun diye bütün bir nibble'ı temizlemekten gelir. Saklı bölge yalnızca 1–2 bitleri, maske $3'tü; $F'i temizlemek, print ile modify olan $4 ile $8'i de siler ve bayraklara uyan her viewer ikisini de reddeder. Ayna görüntüsü hata, maskeyi sıfırdan yukarı kurmaktır: $4, $10 ve $800'ü OR ile birleştirirseniz, 7–8 ile 13–32 bitleri temiz, küçük bir pozitif /P elde edersiniz. Çoğu okuyucu o dosyayı yorum yapmadan açar; çünkü şartname saklı bitleri yok saymalarını söyler — ama değer, revision 3 ve sonrası için kurallara aykırıdır ve katı bir denetleyici şikayet etmeye haklıdır. Üçüncü varyant, bitleri sıfırdan sayan kodda belirir: "3. bit" $8 olur ve program print demek isterken modify verir. Bunların hiçbiri kayıt anında hata üretmez. İzinler, yalnızca yazarın kastettiğinden farklı çıkar
/P -4 ile /P 4294967292 aynı değer mi?
Evet. Aynı 32 bit, iki yazım. PDF integer nesneleri işaretlidir ve Table 22'nin hemen üstündeki not şunu belirtir: yüksek sıradaki saklı bitler 1 olduğu için integer negatif bir sayı olarak yazılır; -4, revision 3 ve sonrası için geleneksel her-şeye-izin değeri. Değeri işaretsiz bir değişkende tutan üreticiler bunun yerine 4294967292 yazar ve gerçek dosyalarda iki yazım da görülür. Şartname, hangi okumanın doğru olduğunu kendi anahtar türetmesinde netleştirir: ISO 32000-1 §7.6.3.3'teki Algorithm 2'nin (d) adımı, P'yi 32-bit işaretsiz bir ikili sayıya çevirir ve dört baytı MD5'e, düşük sıra baytı önce gelecek biçimde verir; böylece revision 2 ile 4 arasında -3904 ile 4294963392 aynı dosya anahtarını üretir. /P'yi işaretli bir 32-bit Integer'da saklayan parser, işaretsiz yazımı taşma sanıp reddeder; değişkeni Cardinal'a çevirirsiniz bu kez negatif yazım düşer, token metnini saklı bir değerle karşılaştıran kod ise bir belgeyi iki belge sayar. Token'i bir Int64'e okuyun, Low(Integer) ile High(Cardinal) arasındaki her şeyi kabul edin ve her karşılaştırmadan önce 32 bite maskeleyin. PDFium Component'in kendi şifre çözme kodu, revision 2'den 7'ye /P'yi böyle okur ve yazıcısı, şartnamedeki notun gösterdiği işaretli biçimi üretir. Snippet'teki saklı bit raporu 14. bitte durur; çünkü temiz bir 13. bit, ISO/TS 32004'ün PDF MAC taşıyan bir dosyayı işaretleyişidir
uses
System.SysUtils;
// Kendi sözlük parser'ınızdan gelen /P token metnini normalize eder
function TryNormalizePermissions(const Token: string;
out Mask: Cardinal): Boolean;
var
Value: Int64;
begin
Result := TryStrToInt64(Token, Value) and
(Value >= Low(Integer)) and (Value <= High(Cardinal));
if Result then
Mask := Cardinal(Value and $FFFFFFFF);
end;
function DescribeReservedBits(Mask: Cardinal): string;
begin
Result := '';
if (Mask and $00000003) <> 0 then
Result := Result + 'bits 1-2 set; ';
if (Mask and $000000C0) <> $000000C0 then
Result := Result + 'bits 7-8 clear; ';
// Bit 13 ($1000) dışarıda bırakılır: ISO/TS 32004, PDF MAC istemek için onu temizler
if (Mask and $FFFFE000) <> $FFFFE000 then
Result := Result + 'bits 14-32 not all set; ';
end;
// Beklenen: iki çağrı da $FFFFFFFC verir ve saklı bit raporu boş kalır
// TryNormalizePermissions('-4', M1);
// TryNormalizePermissions('4294967292', M2);
Bir TPdfPermissions setinden maske kurmak
Her saklı biti zorunlu değerinde bırakarak başlayın ve yalnızca verdiğiniz izinleri OR ile ekleyin; böylece unutulan bir izin ancak bir reddiyeye dönüşebilir. TPdfEncryptOptions.Default — FPdfEncrypt unit'inde — zaten her şeye izin veren $FFFFFFFC'i tutar ve hızlı bir kısıtlama için ondan bir iki bit temizlemek gayet iş görür. Alıcıların ne yapabileceğini listeleyen bir politika, bir set olarak daha iyi okunur; aşağıdaki sabit dizi, sözcüğü çözerken TPdf'in kullandığı bit konumlarıyla hizalıdır. 10. biti, çağıranın ne geçirdiğine bakmaksızın kurun. ISO 32000-2 yazıcılara her zaman kurmayı söyler, PDF 2.0 okuyucuları zaten yok sayar; temiz bırakmanın tek riski, eski yazılımda ekran okuyucuları bloklamaktır
uses
System.SysUtils, PDFium, FPdfEncrypt;
const
PermissionBit: array[TPdfPermission] of Cardinal = (
$0004, // permPrint bit 3
$0008, // permModify bit 4
$0010, // permCopy bit 5
$0020, // permAnnotate bit 6
$0100, // permFillForms bit 9
$0200, // permAccessibility bit 10
$0400, // permAssemble bit 11
$0800); // permPrintHighQuality bit 12
// 7-8 ile 13-32 bitleri kurulu; 1-2 bitleri ile her izin biti temiz
ReservedOnes = Cardinal($FFFFF0C0);
function PermissionsToMask(Allowed: TPdfPermissions): Cardinal;
var
P: TPdfPermission;
begin
Result := ReservedOnes or PermissionBit[permAccessibility];
for P in Allowed do
Result := Result or PermissionBit[P];
end;
procedure SaveFillAndPrintCopy(const Source, Target: string;
const OwnerPassword: AnsiString);
var
Pdf: TPdf;
Opts: TPdfEncryptOptions;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := Source;
Pdf.Active := True;
if not Pdf.Active then
raise Exception.CreateFmt('Could not open %s', [Source]);
Opts := TPdfEncryptOptions.Default; // Burada Revision = erR5
Opts.UserPassword := ''; // dosyayı herkes açabilir
Opts.OwnerPassword := OwnerPassword; // izin setini korur
Opts.Revision := erR6;
Opts.Permissions := PermissionsToMask(
[permPrint, permPrintHighQuality, permFillForms]);
// Beklenen: /P -1084 = 4294966212 ($FFFFFBC4)
Writeln('/P ', Integer(Opts.Permissions), ' = ', Opts.Permissions);
if not Pdf.SaveAsEncrypted(Target, Opts) then
raise Exception.Create('Encrypted save failed');
finally
Pdf.Free;
end;
end;
Beklenen: çıktının Encrypt sözlüğü /P -1084 taşır ve bayraklara uyan bir viewer, tam kaliteli yazdırma ile form doldurmayı sunarken içerik düzenlemeyi, kopyalamayı, yorum eklemeyi ve sayfa birleştirmeyi grileştirir. 13. bit, yazıcının sizin için yönettiği konumlardandır. EnableIntegrityProtection False bırakıldığında SaveAsEncrypted, maskeniz ne derse desin 13. biti kurar; True yapıldığında yazıcı, 13. biti hem /P'de hem /Perms'de temizler. Öteki saklı bitler sizin sorumluluğunuzda kalır — $FFFFF0C0 başlangıç noktası bu yüzden oradadır. Örnek erR6'yı seçer; çünkü R6, ISO 32000-2'nin standardize ettiği AES-256 revisionıdır. Varsayılan kayıt erR5 taşır
/Perms: AES-256 dosyalarında /P'nin şifreli kopyası
Revision 2 ile 4 arasında izin sözcüğü anahtara kaynatılmıştır: /P'nin bir bitini değiştirirseniz MD5 girdisi değişir, türetilen anahtar yanlış olur ve hiçbir şey çözülmez. AES-256 dosyalar o bağı kırar. ISO 32000-2 §7.6.4.3.3'teki Algorithm 2.A, dosya şifreleme anahtarını /UE'den ya da /OE'den açar ve P o türetmede hiç rol oynamaz; böylece düz metin bir /P, kendi başına serbestçe düzenlenebilir. /Perms bu boşluğu kapatır. Algorithm 10 (§7.6.4.4.9) 16 baytlık bir blok doldurur ve onu, dosya anahtarıyla ECB modunda AES-256 ile şifreler:
- 0–3. baytlar: izin sözcüğü, düşük sıra baytı önce; 4–7. baytların hepsi
$FFolacak biçimde 64 bite genişletilir - 8. bayt: ASCII
Tya daF;/EncryptMetadataile uyuşur - 9–11. baytlar: ASCII işaretçi
adb - 12–15. baytlar: okuyucuların yok saydığı rastgele veri
Algorithm 2.A'nın (f) adımı bloğu çözer, adb'yi doğrular ve 0–3. baytların P ile uymasını şart koşar — shall, should değil. Algorithm 13 (§7.6.4.4.12) işaretçi ile izin denetimlerini daha yumuşak should ile yineler ve bir tane daha ekler: 8. baytı EncryptMetadata'ya karşı denetler. PDFium Component, şifreleme katmanını kendi kodu açtığı her yerde bunu katı biçimde okur: AESV4 şifre çözme, PDF MAC doğrulama ve şifreli dosyalara incremental kayıt. Kullanıcı ya da owner şifresi dosya anahtarını açtıktan sonra o kod, dört izin baytının hepsini normalize edilmiş /P ile karşılaştırır, $FF dolgusunu, T/F baytını ve işaretçiyi denetler ve herhangi bir farkta yüklemeyi "PDF encryption permissions validation failed" ile düşürür; çünkü uyuşmazlık, birinin sözlüğü şifrelemeden sonra düzenlediği ve iki kopyanın da yüzeye bakılarak okunamayacağı anlamına gelir
EncryptMetadata karşılaştırması, yazacağınız her okuyucuda özen ister. Pos('/EncryptMetadata false', Dict) > 0 gibi bir kısayol, iki boşluklu /EncryptMetadata false'u, anahtar ile değer arasındaki satır sonunu ya da araya sıkışmış bir yorumu kaçırır — hepsi meşru PDF sözdizimidir. Okuyucu sonra varsayılan true'u varsayar, F'i kusursuzca geçerli bir /Perms içinde bulur ve dosyayı reddeder. Geçerli bir dosya, reddedilmiş. Anahtarı ile değerini token olarak parse edin ve boolean olmayan bir değeri varsayılana çekmek yerine bozuk sayın
ISO/TS 32004, 13. bite bir görev verir: temiz olduğunda belge, okuyucuların denetlemek zorunda olduğu bir PDF MAC taşır. MAC tokeni, trailer'in şifrelenmemiş /AuthCode sözlüğünde durur; yalnızca /AuthCode arayan bir denetim, onu silinmiş bir dosyanın hiç bütünlük korumasıyla üretilmediği sonucuna varır. /KDFSalt'i de silmek gereksinimi gizlemez; çünkü temiz bit /P'de yaşar ve /P, /Perms tarafından doğrulanır. PDFium Component'in AESV4 şifre çözmesi bu yüzden, temiz bir 13. biti ya da artakalmış bir /KDFSalt'i — /AuthCode'suz — soyulmuş bir MAC sayar ve düşürür; bütünlük koruması kapalıyken yazıcı da 13. biti kurar ki çıktısı, taşımadığı bir MAC duyurmasın. O tokenin arkasındaki anahtar hiyerarşisi ile bayt aralığı, PDF 2.0'da AES-GCM şifreleme ve ISO/TS 32004 MAC yazısına aittir; bütün bunlardan önce dosya anahtarını açan iterasyonlu şifre hash'i ise R6 şifre hash'i ve tur sayısı yazısında kapsanır
Permissions mı UserPermissions mı: arayüzünüz hangisini okumalı?
Şu anki oturumun ne yapabileceğine karar vermek için Permissions'i, dosyanın sıradan bir alıcıya ne verdiğini raporlamak için UserPermissions'i okuyun. TPdf.Permissions, PDFium'un FPDF_GetDocPermissions fonksiyonunu sarar ve açık bu belgeye verilmiş erişimi anlatır; hangi şifrenin açtığına bağlıdır. TPdf.UserPermissions, FPDF_GetDocUserPermissions'i sarar ve hangi şifre kullanılmış olursa olsun /P'den gelen kullanıcı erişim iznini raporlamaya devam eder; owner şifresi dosyayı açtığında ikisi farklılaşabilir. İkisi de LongWord'tür; GrantedPermissions ile UserGrantedPermissions bunları TPdfPermissions setlerine çözer, HasPermission ile UserHasPermission tek bir üyeyi sınar. SecurityHandlerRevision, dosyanın hiç güvenlik işleyicisi olup olmadığını söyler. Aşağıdaki rutin, ham sözcüğün iki tam sayı görünümünü çözülmüş setin yanına yazdırır — bir dosyanın "neden yazdırmadığını" hızla görmek için
uses
System.SysUtils, System.TypInfo, PDFium;
procedure DumpPermissions(const FileName, Password: string);
var
Pdf: TPdf;
Mask: LongWord;
Granted: TPdfPermissions;
P: TPdfPermission;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := FileName;
Pdf.Password := Password; // Active'ten önce atanır
Pdf.Active := True;
if not Pdf.Active then
raise Exception.CreateFmt('Could not open %s', [FileName]);
if Pdf.SecurityHandlerRevision <= 0 then
begin
Writeln('Not encrypted: no /P to decode');
Exit;
end;
Mask := Pdf.UserPermissions;
Granted := Pdf.UserGrantedPermissions;
Writeln(Format('R%d /P as Integer %d, as Cardinal %u ($%.8x)',
[Pdf.SecurityHandlerRevision, Integer(Mask), Mask, Mask]));
for P := Low(TPdfPermission) to High(TPdfPermission) do
Writeln(Format(' %-22s %s',
[GetEnumName(TypeInfo(TPdfPermission), Ord(P)),
BoolToStr(P in Granted, True)]));
if not Pdf.HasPermission(permCopy) then
Writeln(' this session may not copy text');
finally
Pdf.Free;
end;
end;
// Daha önce yazılan dosya için beklenen:
// R6 /P as Integer -1084, as Cardinal 4294966212 ($FFFFFBC4)
// permPrint True, permModify False, permCopy False, ...
Zorunlu kılma işi, konak uygulamaya düşer
PDFium, bayraklar ne derse desin render eder, metin çıkarır ve sayfaları rasterleştirir. Motor izinleri raporlar ve politikayı çağırana bırakır. Kullanıcı şifresini elinde tutan, çözülmüş içeriği de elinde tutar — boş bir kullanıcı şifresiyle bu herkestir; dolayısıyla gizlilik, yalnızca okuyucunun sahip olmadığı bir kullanıcı şifresinden gelebilir. Denetimi, araç çubuğu düğmelerini gizlemek yerine her komut işleyicinin (kopyalama, dışa aktarma, sayfa silme, yazdırma) içine koyun; kısayollar, sağ tık menüleri ve otomasyon araç çubuğunu her seferinde atlatır — Delphi uygulamaları için güvenli bir PDF önizleme bölmesi yazısındaki katmanlama da budur. Mesela bir yazdırma komutu, bir şey kuyruğa almadan önce permPrint'i sınar
AES-256 bir dosyada düz metindeki -1084, /Perms'in çözülen ilk dört baytı C4 FB FF FF okuyana dek hiçbir şey kanıtlamaz. O bayt bayt karşılaştırma, PDFium Component'in AESV4, PDF MAC ve şifreli incremental kayıt kodunun içinde çalışır