少し要因が見えてきたので、タイトル変更しました。
→解決しました。
結論は追記およびコメント参照ください。
経緯
とあるpdfファイルからテキストを抽出できないか 1 調べていたところ
<<.../FlateDecode...>stream
...endstream
のデータを復号できずに困り、pdfフォーマットの解説書を読んだが、そのファイル自身が、自ら定義しているフォーマットに違反しているようにしか見えなかった。
pdfフォーマットの解説書
https://www.adobe.com/devnet/pdf/pdf_reference.html 内のリンク
Document Management – Portable Document Format – Part 1: PDF 1.7, First Edition - PDF32000_2008.pdf
RFC1950 (とRFC1951)に準拠しているように読み取れる。
該当のPDFのデータ
"stream
" 2 以降の9A FC
~が、データであり、zlibフォーマットに従っているはずだが、
RFCs 1950によると
-
先頭から2byteが
CMF
(1byte),FLG
(1byte)の順で配置され、CMF
の下位4bit(Compresstion method)は、値8
しか定義されていない。(ただ、使うなとは書いていない。) -
先頭から2byteをビッグエンディアンの数値としてみたときに、31で割り切れること
The FCHECK value must be such that CMF and FLG, when viewed as
a 16-bit unsigned integer stored in MSB order (CMF*256 + FLG),
is a multiple of 31.
とあるが、下記のよるに、0x9AFC
(39676)は31では割り切れない。(余り:27)
であり、準拠しているように見えない。
どうやって復号すりゃいいんだ・・。へるぷみー
とりあえずJavaで試してみたが復号できない。
ちなみにC#で先頭2byte捨ててDeflateStream
で読み込む方法もダメでした。
要因→追記参照
試してみたDeflate復号用のソースコード(Java)
78 9C
とかで始まっているデータで動確済み。
ソースコード
import java.io.*;
import java.util.zip.*; // to use Inflater
public class DeflateTest
{
public static byte[] GetBytes(String path, long srcOffset, int srcLength) throws IOException
{
byte[] b = new byte[srcLength];
int n = 0;
FileInputStream fs = null;
try {
fs = new FileInputStream(path);
fs.skip(srcOffset);
n = fs.read(b, 0, srcLength);
if (n != srcLength) {
b = null;
}
}
catch (FileNotFoundException e) {
System.out.println(e);
return null;
}
catch (IOException e) {
System.out.println(e);
return null;
}
finally {
if (fs != null) {
fs.close();
}
}
return b;
}
public static void DecodeFile(String path, long srcOffset, int srcLength) throws IOException
{
byte[] buf = GetBytes(path, srcOffset, srcLength);
if ( buf == null ) {
return;
}
Inflater inf = new Inflater();
inf.setInput(buf, 0, srcLength);
byte[] tmp = new byte[1024];
FileOutputStream fos = null;
try {
fos = new FileOutputStream("test_out.dat");
while ( !inf.finished() ) {
int len = inf.inflate(tmp);
if ( len > 0) {
fos.write(tmp, 0, len);
}
}
}
catch (DataFormatException e) {
System.out.println(e);
}
finally {
if ( fos != null ) {
fos.close();
}
}
}
public static void main(String[] args) throws IOException
{
if (args.length != 3) {
System.out.println("Parameter error.");
return;
}
long offset = 0;
int length = 0;
try {
offset = Long.decode(args[1]);
length = Integer.decode(args[2]);
}
catch (Exception e) {
System.out.println("Parameter parse error.");
return;
}
DecodeFile(args[0], offset, length);
}
}
コンパイル方法:javac DeflateTest.java
実行方法:java DeflateTest ファイルパス 開始バイト位置 バイト長
>java DeflateTest PDF32000_2008.pdf 0x434A 542
java.util.zip.DataFormatException: incorrect header check
追記
streamが暗号化されている
保護されたpdfだと事前に別のデコード処理が要るとかか→あたりっぽい。
(pdf最後尾(trailer)に/Encrypt
があるかどうかで判断できそう。)
AESの場合は、さらに、streamの先頭16byte分にAES CBC modeのinitial vectorが配置されているらしい。
下記が非常に参考になりました。
pdfの暗号化 - https://qiita.com/mtgto/items/e54fb19d0547590ca791
差分エンコーディング?
さらに差分エンコーディングしている可能性がある。不明。
/Decodeparms
, /Predictor
(PLRMを参照)が関係しそう?
http://7shi.hateblo.jp/entry/20110207/1297053867
今回は関係なかった。