密码学基础3:密钥文件格式完全解析

这是密码学笔记第三篇。之前两篇分析了 RSA 算法和椭圆曲线密码学的基本原理,从中可以知道 RSA 算法的本质是大整数质数因子分解,椭圆曲线密码学的本质是曲线上的打点,朴素的原理后面处处闪耀着数学之光。从理论到实践,这篇文章来解析下日常使用的密钥文件格式。

文章主要解决下面几个问题:

  • 使用 opensslssh-keygen 生成的 RSA 和 ECC 的密钥文件里面存了什么内容,用的什么编码规则?
  • 有些 RSA 私钥头部是 -----BEGIN RSA PRIVATE KEY-----,而有些又是 -----BEGIN PRIVATE KEY-----,它们存储的内容有什么区别?
  • 使用 openssl 生成的公钥跟 ssh-keygen 生成的密钥对中的公钥格式不一样,它们有什么区别?
  • opensslssh-keygen 都支持输入密码对私钥加密以增强安全性,它们加密方式分别是什么?有什么不同之处吗?

1 ASN.1

ASN.1(Abstract Syntax Notation One) 是一种数据描述语言,跟 protobuf 和 Thrift 这些接口描述语言类似,它通过模块(module) 来描述数据结构。ASN.1 最早用于电信领域,后来在计算机密码学中也有广泛应用。

ASN.1 定义了一些数据类型来描述数据结构,包括基础类型(如整数类型 INTEGER,布尔类型 BOOLEAN,字符串类型 OCTET STRING,BIT STRING)和结构化类型(如结构体类型 SEQUENCE,列表类型 SEQUENCE OF 等),完整类型列表见 [ASN.1 Types]。类型通常都有个类型标签,其中类型标签分为通用的、应用自定义的、上下文特定的、以及私有的 4 种类型。其中通用的类型标签如下:

Type Tag Number
INTEGER 0x02
BIT STRING 0x03
OCTET STRING 0x04
NULL 0x05
OBJECT IDENTIFIER 0x06
SEQUENCE and SEQUENCE OF 0x10
IA5String 0x16
UTCTime 0x17

下面用ASN.1定义了一个名为 FooProtocol 的模块,其中包含一个结构体类型字段 FooQuestion。结构体 FooQuestion 包括一个整形字段 id 和一个字符串类型字段 question(IA5String 是不包括控制字符的字符串类型)。

FooProtocol DEFINITIONS ::= BEGIN
    FooQuestion ::= SEQUENCE {
        id INTEGER,
        question       IA5String
    }
END

ASN.1 只是描述了数据结构,并没有指定怎么编码数据。因此,出现了多种编码规则以方便数据在网络上传输和不同终端间交互。比较常见的有 XER, JER, BER, DER等。如待编码的数据如下:

myQuestion FooQuestion ::= SEQUENCE {
    id 5,
    question "Anybody there?"
}

使用各编码规则编码结果如下,其中 XER 和 JER 不用多说,BER 和 DER 是最常见的密钥文件编码规则,下一节详细分析。

                    XER(XML Encoding Rules)
            <FooQuestion>
                <id>5</id>
                <question>Anybody there?</question>
            </FooQuestion>

                    JER(JSON Encoding Rules)
    { "id" : 5, "question" : "Anybody there?" }
        
                    BER(Basic Encoding Rules)
        30 13 02 01 05 16 0e 41 6e 79 62 6f 64 79 20 74 68 65 72 65 3f

                    DER(Distinguished Encoding Rules)
        30 13 02 01 05 16 0e 41 6e 79 62 6f 64 79 20 74 68 65 72 65 3f

2 编码规则

前文提到 ASN.1 只是定义了数据结构,并未规定具体的编码方式,与之对应的有多种编码规则。ASN.1 与特定的编码规则一起通过使用独立于计算机架构和编程语言的方法来描述数据结构,为结构化数据的表示、编码、传输、解码提供了便利。本节主要介绍密钥和证书中常见的编码规则 BER,DER,以及由 DER 衍生出的密钥文件格式 PEM。

BER (Basic Encoding Rules)

BER 是基础编码规则,编码结构包括类型标志、长度,值以及结束符(可选),每个字段以 8bit 即字节进行分割。

----------------------------------------------------
| Identifier | Length | Contents | End-of-contents |
|   octets   | octets |  octets  |      octets     |
----------------------------------------------------

Identifier: 类型标志,就是ASN.1 规定的类型,只是除了标签号(tag number)外,还加了 3 位,第 7,8 位用于区分是通用的标签类型还是其他标签类型, 第 6 位 用于区分是基础类型还是结构化类型。Identifier 结构如下,后面我们会看到密钥中的结构体类型 SEQUENCE 的 Identifier 为 0x30,即是由这个格式而来(0011000)。

---------------------------------
| 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
---------------------------------
|Class  |P/C| Tag Number        |
---------------------------------
  • Class:如果是通用类型标签则为 00,应用自定义的类型标签则为 01,上下文特定类型标签 10,私有类型标签 11。
  • P/C:如果是基础类型,则为 0,结构化类型为 1。
  • Tag Number:就是 ASN.1 中定义的数据类型标签号。

Length: 分三种情况,

  • 1)数据长度 < 128:则 Length 的 8bit首位为0,其他7位表示数据长度。
  • 2)数据长度 >= 128:则 Length 的第一个8bit为 0x8?,其中 ? 是后面跟的是长度。比如 0x81 表示后面一个字节为长度,如果是 0x82 则表示后面两个字节为长度,以此类推。
  • 3)如果数据长度未知,则 Length=0x80,并增加 End-of-contents=00 00 结束标记。

Contents & End-of-contents: 数据内容。对于未知数据长度的数据类型,才有 End-of-contents,为00 00

实例:

  • 使用 OCTET STRING 编码字符串 Hello,为 04 05 48 65 6C 6C 6F,即类型为 04,长度为 05,内容为 0x48 65 6C 6C 6F,即 Hello 的 ASCII 码。
  • 使用 INTEGER 编码整数 129,为 02 81 81
  • 结构化类型是包含了多个数据类型的复合类型,后面详细分析。

DER (Distinguished Encoding Rules)

DER 是典型的 Tag-Length-Value(TLV) 编码方式,是 PKCS 密钥体系常用的编码。
DER 是 BER 的子集,编码规则几乎一样,不过去掉了 BER 的一些灵活性,多了几个限制:

  • 如果数据长度在 0-127 之间,则 Length 必须使用第 1 种编码方式。
  • 如果数据长度 >= 128,则 Length 必须使用第 2 种编码方式,且 Length 必须用最少的字节编码,如果能用 2 字节的则不能用 3 字节。
  • 数据要用明确长度的编码方式,不支持 Length 的第3种编码即未知数据长度+结束标记的方式。

注意:ASN.1 规定整型 INTEGER 需要支持正整数、负整数和零。BER/DER 使用大端模式存储 INTEGER,并通过最高位来编码正负数(最高位0为正数,1为负数)。 如果密钥参数值最高位为 1,则 BER/DER 编码会在参数前额外加 0x00 以表示正数,这也就是为什么有时候密钥参数前面会多出1字节 0x00 的原因。

PEM(Privacy Enhanced Mail): 因为 DER 编码是二进制数据,早期的 Email 不能发送附件,也不方便直接传输二进制数据([原因]),因此密钥文件通常会在 DER 编码基础上进行 Base64 编码,这就是经常看到的密钥文件格式 PEM。PEM 最早是用来增强邮件安全性的,不过没有被广泛接受,最后却是在密码学中得到了发扬光大,如 openssl 和 ssh-keygen 工具生成的公私钥文件默认都采用 PEM 格式。需要注意的是,PEM 本身不是 ASN.1 的编码规则,它只是 Base64-encoded DER

3 密钥格式解析

ASN.1 编码规则只定义了数据编码方式,但是并没有赋予数据意义。公钥密码学标准 PKCS (Public Key Cryptography Standards) 和公钥基础设施 PKIX(Public-Key Infrastructure X.509) 等使用 ASN.1 的类型定义密钥和证书的格式和编码,以描述公私钥和证书属性。 需要注意,PKCS 虽然名字是公钥密码学标准,它其实也包括私钥格式标准。 这两个标准的内容浩瀚如烟,本节只分析常见几种密钥相关的部分。

3.1 PKCS #1

PKCS #1 是 RSA Cryptography Specifications,即 RSA 密码学规范,它在 [rfc8017] 中有详细说明,定义了 RSA 密钥文件的格式和编码方式,以及加解密、签名、填充的基础算法。

RSA 密钥格式

RSA 公私钥的 ASN.1 类型定义如下,根据类型定义和数据编码,就能解析出 RSA 公私钥中的参数了(参数含义请参考我之前的《RSA算法原理解析》一文)。


 RSAPublicKey ::= SEQUENCE {
        modulus           INTEGER,  -- n
        publicExponent    INTEGER   -- e
 }
         
 RSAPrivateKey ::= SEQUENCE {
        version           Version,
        modulus           INTEGER,  -- n
        publicExponent    INTEGER,  -- e
        privateExponent   INTEGER,  -- d
        prime1            INTEGER,  -- p
        prime2            INTEGER,  -- q
        exponent1         INTEGER,  -- d mod (p-1)
        exponent2         INTEGER,  -- d mod (q-1)
        coefficient       INTEGER,  -- (inverse of q) mod p
        otherPrimeInfos   OtherPrimeInfos OPTIONAL
}

使用 openssl 生成的一对 RSA 公私钥 (示例为方便展示,用的 1024 位密钥,实际中请使用 2048 位以上)

$ openssl genrsa -out prikey.p1 1024
$ openssl rsa -in prikey.p1 -pubout -RSAPublicKey_out > pubkey.p1

PKCS#1 格式解析如下:公钥的 SEQUENCE 包括 RSA 公钥参数 n 和 e 两个属性。RSA 私钥则首先是版本号 0,然后是 RSA 私钥的 8 个参数。

PKCS#1 公钥
PKCS#1 私钥

加密私钥

可以对 PKCS#1 的私钥进行加密,如 ssh-keygen 可以指定 passphrase (测试的密码是 testtest)加密 RSA 私钥,加密后的私钥 enc_prikey.p1 格式如下:

-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,8C2A8D6593F411D7336B842037B5200B

EncryptedRSAPrivateKey
-----END RSA PRIVATE KEY-----

DEK-Info 里面指明了加密算法是 AES-128-CBC,IV 是 8C2A8D6593F411D7336B842037B5200B,AES加密的实际密码=md5(设定密码 + IV的前8个字节)。可以使用 openssl aes-128-cbc 验证加密结果是否与 EncryptedRSAPrivateKey 一致。

$ tail -n +2 prikey.p1 | grep -v 'END RSA' | base64 -d | 
openssl aes-128-cbc -e -iv 8C2A8D6593F411D7336B842037B5200B -K $(python -c "exec(\"import hashlib\\nprint hashlib.md5(bytearray('testtest') + bytearray.fromhex('8C2A8D6593F411D7')).hexdigest()\")") | base64

3.2 PKCS #8

PKCS#8 是 Private-Key Information Syntax Standard,即私钥格式相关的标准,它不像 PKCS#1 只支持 RSA,而是支持各种类型的私钥。PKCS#8 私钥文件格式中首尾并未说明私钥算法类型,算法类型在数据中标识。PKCS#8 中的私钥也支持加密。

未加密私钥格式

未加密私钥格式的 ASN.1 类型定义如下(参见 [rfc5958] ):

    OneAsymmetricKey ::= SEQUENCE {
        version                   Version,
        privateKeyAlgorithm       PrivateKeyAlgorithmIdentifier,
        privateKey                PrivateKey,
        attributes            [0] Attributes OPTIONAL,
       ...,
       [[2: publicKey        [1] PublicKey OPTIONAL ]],
       ...}

        Version ::= INTEGER  # 版本号。
    
        PrivateKeyAlgorithmIdentifier ::= SEQUENCE  { # 密钥算法标识
                algorithm               OBJECT IDENTIFIER,
                parameters              ANY DEFINED BY algorithm OPTIONAL  }
    
        PrivateKey ::= OCTET STRING # 不同类型的私钥格式不同,比如 RSA 的是 RSAPrivateKey类型,而 ECC 的是 ECPrivateKey 类型。
            
        Attributes ::= SET OF Attribute # 跟公钥相关的属性,比如证书什么的,在公私钥中通常为空。
        
        PublicKey ::= BIT STRING # 不同类型密钥包含的公钥内容也不同。

RSA 私钥格式

可以使用 openssl 将 PKCS#1 格式的私钥 prikey.p1 转换成 PKCS#8 格式的 prikey.p8,如下:

$ openssl pkcs8 -in prikey.p1 -topk8 -out prikey.p8 -nocrypt

私钥格式解析如下:

PKCS#8 RSA私钥格式
  • version:版本号,目前值为 0。
  • privateKeyAlgorithm:私钥算法, rsaEncryptionOBJECT IDENTIFIER1.2.840.113549.1.1.1,具体含义参见 [这里]
  • privateKey:私钥,OCTET STRING 类型,里面其实封装了一个 RSAPrivateKey 类型,跟 PKCS#1 一样。
  • attributes 和 publicKey 为空。

ECC 私钥格式

椭圆曲线类型的私钥格式在 rfc5915 中定义如下:

ECPrivateKey ::= SEQUENCE {
     version        INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
     privateKey     OCTET STRING,
     parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
     publicKey  [1] BIT STRING OPTIONAL
}

使用 openssl 创建一个 PKCS#8 格式的 ecc 密钥,采用 prime256v1 曲线:

# 生成传统格式的 ECC 私钥,类似 PKCS#1 那样,只包含 privateKey,密钥类型在头部 -----BEGIN EC PRIVATE KEY----- 标识,椭圆曲线在 parameters 标识。
$ openssl ecparam -name prime256v1 -genkey -noout -out ecc_prikey.tradfile

# 转换为 PKCS#8 格式
$ openssl pkcs8 -topk8 -in ecc_prikey.tradfile -out ecc_prikey.p8 -nocrypt
$ cat ecc_prikey.p8
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgMbahscIGpSZ6NULI
iQ/pTI9ZcvFdXKtjN1bAGO2bxvahRANCAATwq1k9rx/8neP8MqVR7UuJ98bLFsU5
jpueH0ougZNWrsKUki0cgKDGrb3C8Q2NMRO336ve22Xk674lk/ZDHkAV
-----END PRIVATE KEY-----

ASN.1 格式解析如下:

PKCS#8 ECC私钥格式
  • 前面部分是算法标识 1.2.840.10045.2.1(ecPublicKey) 和 1.2.840.10045.3.1.7 (prime256v1)
  • 后面是私钥信息,其中包括了版本号 1,OCTET STRING 类型私钥 31B6A1...F6,BIT STRING 类型的公钥 0000 0100 1111 0000...

当然通过 openssl 可以直接解析出公私钥和曲线类型,如下:

$ openssl ec -in ecc_prikey.p8 -noout -text
read EC key
Private-Key: (256 bit)
priv:
    31:b6:a1:b1:c2:06:a5:26:7a:35:42:c8:89:0f:e9:
    4c:8f:59:72:f1:5d:5c:ab:63:37:56:c0:18:ed:9b:
    c6:f6
pub:
    04:f0:ab:59:3d:af:1f:fc:9d:e3:fc:32:a5:51:ed:
    4b:89:f7:c6:cb:16:c5:39:8e:9b:9e:1f:4a:2e:81:
    93:56:ae:c2:94:92:2d:1c:80:a0:c6:ad:bd:c2:f1:
    0d:8d:31:13:b7:df:ab:de:db:65:e4:eb:be:25:93:
    f6:43:1e:40:15
ASN1 OID: prime256v1
NIST CURVE: P-256

在上一篇《椭圆曲线密码学原理分析》一文知道,椭圆曲线的密钥生成其实就是一个公式 P = nG,n 就是私钥,G 是基点,P 是公钥。注意到这里公钥的第一个字节 04 表示公钥格式是 uncompressed format,即非压缩格式,也就是把点的 X 和 Y 坐标合到一起作为公钥。压缩格式就是只用 X 坐标或者 Y 坐标中的一个,另一个坐标根据曲线方程可以求得([rfc5480] 有详细说明)。可以通过 libnum 库来验证下公私钥的准确性。

$ cat ecc.py 
from libnum.ecc import Curve
curve = Curve(
        a=0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc,
        b=0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b,
        p=0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff,
        g = (0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296,
            0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5)
)

pri = 0x31b6a1b1c206a5267a3542c8890fe94c8f5972f15d5cab633756c018ed9bc6f6
pub = curve.power(curve.g, pri)
print hex(pub[0]), hex(pub[1])
$ python ecc.py 
('0xf0ab593daf1ffc9de3fc32a551ed4b89f7c6cb16c5398e9b9e1f4a2e819356aeL', 
'0xc294922d1c80a0c6adbdc2f10d8d3113b7dfabdedb65e4ebbe2593f6431e4015L')

加密私钥格式

PKCS#8 里面对私钥加密提供了 PBES2(Password-Based Encryption Scheme 2)加密模式支持。通过 PBKDF2(Password-Based Key Derivation Function 2) 对原始密码进行多次哈希处理作为加密密码以增强破解难度,然后用对称加密算法 AES 或者 DES 对私钥进行加密。

PBKDF2 是一种 CPU 密集型算法,但是如果使用 GPU 阵列或者 FPGA 来破解还是相对容易。在密码存储中现在更倾向于用 Bcrypt,它不仅是 CPU 运算密集,而且是内存密集,破解难度会更高一些。不过总的来说, PBKDF2 比 ssh-keygen 的 md5 方式生存密码安全性会高很多。

PKCS#8 加密类型私钥的 ASN.1 类型定义如下:

EncryptedPrivateKeyInfo ::= SEQUENCE {
     encryptionAlgorithm  EncryptionAlgorithmIdentifier,
     encryptedData        EncryptedData }

     EncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
                                        { CONTENT-ENCRYPTION,
                                          { KeyEncryptionAlgorithms } }

     EncryptedData ::= OCTET STRING

使用 openssl 对 PKCS#8 格式的密钥加密是很方便的,默认就支持(生成密钥时不加 -nocrypt 参数即可)。加密后的 ecc_prikey.p8 格式解析如下:

加密的 PKCS#8 ECC私钥格式
  • 其中加密模式是 pkcs5PBES2,密钥生成算法是 PBKDF2,参数 salt= E20EED9A112B7BFA,iteration=2048,哈希算法是 hmacWithSHA256
  • 对称加密算法是 aes256-cbc,参数 iv=27579581D081AEDA083889370232AD1A
  • 最后一行 11E9C5C2.... 就是加密私钥 encryptedData。

加密过程解析:

  • 先使用密钥生成算法 PBKDF2 生成加密密码,python 可以用 backports.pbkdf2 模块。
import os, binascii
from backports.pbkdf2 import pbkdf2_hmac

salt = binascii.unhexlify('E20EED9A112B7BFA')
passwd = b"testtest"
key = pbkdf2_hmac("sha256", passwd, salt, 2048, 32)
print("Derived key:", binascii.hexlify(key))

# 输出: ('Derived key:', 'bf48084fd98fcbacd8e024166efb7232c897282fe7e4ff836db3f3d81e32ede9')
  • 然后使用 openssl 的 aes256-cbc 加密原私钥,可以验证跟 encryptedData 是一样的。
$ tail -n +2 ecc_prikey.p8 | grep -v 'END '| base64 -d | 
openssl aes-256-cbc -e -iv 27579581D081AEDA083889370232AD1A -K bf48084fd98fcbacd8e024166efb7232c897282fe7e4ff836db3f3d81e32ede9 | hexdump -C

00000000  11 e9 c5 c2 4c c3 2d bb  fa 84 b9 fb db f1 d1 ff  |....L.-.........|
00000010  f0 6a 5b fa c3 a6 88 cd  02 4c ac 52 84 f4 cb c1  |.j[......L.R....|
......
00000080  8f 72 96 7a 58 aa 1f 5a  6f c1 bf dc 43 1a 46 26  |.r.zX..Zo...C.F&|

3.3 PKIX

前面提到 PKCS#8 定义了通用的私钥格式支持各类私钥,在 PKIX ([rfc5280]) 中也定义了通用的公钥格式。其中包括算法标识和公钥内容,算法标识 AlgorithmIdentifier 与前面私钥中的 PrivateAlgorithmIdentifier 是类似的。

SubjectPublicKeyInfo  ::=  SEQUENCE  {
        algorithm         AlgorithmIdentifier,
        subjectPublicKey  BIT STRING }

        AlgorithmIdentifier  ::=  SEQUENCE  {
                algorithm               OBJECT IDENTIFIER,
                parameters              ANY DEFINED BY algorithm OPTIONAL  
        }

将之前的 PKCS#1 格式的 RSA 公钥转换成 PKIX 的格式:

$ openssl rsa -RSAPublicKey_in -in ../pk1/pubkey.p1 -pubout > pubkey.pkix

PKIX 格式的公钥解析如下,包括公钥算法 rsaEncryption 和 RSA 公钥参数 n 和 e。

PKIX公钥格式

3.4 openssl 和 ssh-keygen 生成公钥格式区别

相信大家会发现 ssh-keygen -t rsa 生成的 RSA 密钥对中公钥格式跟 PKCS#1 和 PKIX 中的都不一样。ssh-keygen 生成的公钥 id_rsa.pub 如下所示:

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC+gCiA//vUMu/2dYj9oGUpY2TCw5/AtkfI2cWvl7hOkliQd7uI61gE9BV5w+Ib+HnjAB9lFYS4A8rlpRlkH9a+mCN2K/Oh5dhoonxat4qeHB5XDvmImUfdOGayT5l176KWP4ftGJt+8ygRpo05zcbuBrd/KxFZ7KDiQyXRvRv9mw== vagrant@stretch

这是 openssh 使用的一种专属格式:

[type-name] [base64-encoded-ssh-public-key] [comment]

其中 base64-encoded-ssh-public-key 并没有使用 ASN.1 的数据类型定义和 DER 编码,而是使用的 SSH 协议定义的格式(见 [rfc4251]),分为 3 部分:

string    "ssh-rsa"
mpint     e
mpint     n

这里的 string 类型和 mpint 类型在 rfc4251 中定义,其中 mpint是使用字符串的方式来存储整数,它们都会在数据前先用4字节存储数据长度。id_rsa.pub 解析后格式如下所示:

00 00 00 07  73 73 68 2d 72 73 61  | 长度 7,值 ssh-rsa|
00 00 00 03  01 00 01  | e 长度 3,值 0x010001 |
00 00 00 81  00 be 80 28 80 ff fb d4 |n 长度 127,值 0x00 be 80 28 80...|
......

也可以将 ssh-keygen 生成的公钥转换成 PKCS#1 的格式:

$ ssh-keygen -f id_rsa.pub -e -m pem > pubkey.p1

4 总结

PKCS 和 PKIX 使用 ASN.1 定义了密钥的数据结构,并使用 DER 编码规则编码密钥,最终使用 PEM (Base64-encoded DER) 格式将密钥数据存储在密钥文件中。

PKCS#1 首部会标识密钥算法类型,PKCS#8 则是在密钥数据中有字段专门存储密钥算法。openssh 使用的公钥格式是 SSH 协议中定义的,虽然参数值一样,但是编码方式与 PKCS 和 PKIX 标准都不同。

ssh-keygen 是对原始密码和初始向量经过一个简单规则 md5 后生成加密密码,然后使用 aes128-cbc 对称加密。而 openssl 则是对原始密码采用 PBKDF2 算法生成加密密码,然后使用 aes256-cbc 对称加密,openssl 的加密方式更安全一些。

参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容