PGP 对于JSON的加解密

引言:

目前CSDN上,大部分都是对于文本的PGP加解密博文,但实际开发中,可能会与渠道对接使用PGP加解密报文,所以这里整理了下,对文本的加解密工具,希望对你有帮助。

一、引入PGP的依赖

        <!-- PGP 加解密 依赖-->
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcpg-jdk15on</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15on</artifactId>
            <optional>true</optional>
        </dependency>

二、编写工具类

package com.bop.util.pgp;

import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;

public class BCPGPUtils {
    public static PGPPublicKey readPublicKey(String fileName) throws IOException, PGPException {
        InputStream keyIn = new BufferedInputStream(new FileInputStream(fileName));
        PGPPublicKey pubKey = readPublicKey(keyIn);
        keyIn.close();
        return pubKey;
    }

    public static PGPPublicKey readPublicKey(InputStream in) throws IOException, PGPException {
        PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(in),
                new JcaKeyFingerprintCalculator());
        Iterator<PGPPublicKeyRing> keyRings = pgpPub.getKeyRings();
        while (keyRings.hasNext()) {
            PGPPublicKeyRing keyRing = keyRings.next();
            Iterator<PGPPublicKey> publicKeys = keyRing.getPublicKeys();
            while (publicKeys.hasNext()) {
                PGPPublicKey key = publicKeys.next();
                if (key.isEncryptionKey()) {
                    return key;
                }
            }
        }
        throw new IllegalArgumentException("Can't find encryption key in key ring.");
    }

    public static PGPSecretKey readSecretKey(String fileName) throws IOException, PGPException {
        InputStream keyIn = new BufferedInputStream(new FileInputStream(fileName));
        PGPSecretKey secKey = readSecretKey(keyIn);
        keyIn.close();
        return secKey;
    }

    public static PGPSecretKey readSecretKey(InputStream in) throws IOException, PGPException {
        PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(in),
                new JcaKeyFingerprintCalculator());
        Iterator<PGPSecretKeyRing> keyRings = pgpSec.getKeyRings();
        while (keyRings.hasNext()) {
            PGPSecretKeyRing keyRing = keyRings.next();
            Iterator<PGPSecretKey> secretKeys = keyRing.getSecretKeys();
            while (secretKeys.hasNext()) {
                PGPSecretKey key = secretKeys.next();
                if (key.isSigningKey()) {
                    return key;
                }
            }
        }
        throw new IllegalArgumentException("Can't find signing key in key ring.");
    }

    /**
     * Load a secret key ring collection from keyIn and find the private key
     * corresponding to keyID if it exists.
     */
    public static PGPPrivateKey findSecretKey(PGPSecretKeyRingCollection pgpSec, long keyID, char[] pass)
            throws PGPException {
        PGPSecretKey pgpSecKey = pgpSec.getSecretKey(keyID);
        if (pgpSecKey == null) {
            return null;
        }
        return pgpSecKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(pass));
    }

    public static PGPPrivateKey findPrivateKey(InputStream keyIn, long keyID, char[] pass)
            throws IOException, PGPException {
        PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(keyIn),
                new JcaKeyFingerprintCalculator());
        return findPrivateKey(pgpSec.getSecretKey(keyID), pass);
    }

    public static PGPPrivateKey findPrivateKey(PGPSecretKey pgpSecKey, char[] pass)
            throws PGPException {
        if (pgpSecKey == null)
            return null;
        return pgpSecKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(pass));
    }
}

三、编写加密类

package com.bop.util.pgp;

import lombok.SneakyThrows;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.CompressionAlgorithmTags;
import org.bouncycastle.bcpg.HashAlgorithmTags;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPCompressedData;
import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedData;
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
import org.bouncycastle.openpgp.PGPOnePassSignature;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator;

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.Security;

/**
 * PGP 加密类
 *
 * @author neal.armstrong
 * @version 1.0
 * @since 2021.11.22
 */
public class PgpMemEncryptor {
    private final String publicKeyPath;
    private final String privateKeyPath;

    private InputStream PUBLIC_KEY;

    private PGPPublicKey publicKey;
    private PGPPrivateKey privateKey;

    public PgpMemEncryptor(String publicKeyPath, String privateKeyPath) {
        this.publicKeyPath = publicKeyPath;
        this.privateKeyPath = privateKeyPath;
    }

    public PGPPublicKey getPublicKey() {
        if (publicKey == null) {
            try {
                publicKey = BCPGPUtils.readPublicKey(publicKeyPath);
            } catch (IOException | PGPException e) {
                e.printStackTrace();
            }
        }
        return publicKey;
    }

    public PGPPrivateKey getPrivateKey(String purhapse) {
        if (privateKey == null) {
            try {
                PGPSecretKey secretKey = BCPGPUtils.readSecretKey(privateKeyPath);
                privateKey = secretKey
                        .extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider())
                                .build(purhapse.toCharArray()));
            } catch (PGPException | IOException e) {
                e.printStackTrace();
            }
        }
        return privateKey;
    }

    @SneakyThrows
    private InputStream getPublicIs() {
        if (PUBLIC_KEY == null) {
            PUBLIC_KEY = Files.newInputStream(Paths.get(publicKeyPath));
        }
        return PUBLIC_KEY;
    }

    private static void getProvider() {
        Provider provider = Security.getProvider("BC");
        if (provider == null) {
            provider = new BouncyCastleProvider();
            Security.addProvider(provider);
        }
    }

    public String encryptFile(String inputFile, boolean withIntegrityCheck) throws IOException, PGPException {
        getProvider();
        byte[] rawText = inputFile.getBytes();

        ByteArrayOutputStream encOut = new ByteArrayOutputStream();
        PGPPublicKey pubKey = BCPGPUtils.readPublicKey(getPublicIs());
        ArmoredOutputStream outputStream = new ArmoredOutputStream(encOut);

        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
        PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(CompressionAlgorithmTags.ZIP);
        PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
        OutputStream open = lData.open(comData.open(bOut), PGPLiteralData.BINARY, PGPLiteralData.CONSOLE, rawText.length, PGPLiteralData.NOW);
        open.write(rawText);
        lData.close();
        comData.close();

        BcPGPDataEncryptorBuilder builder = new BcPGPDataEncryptorBuilder(PGPEncryptedData.AES_256);
        builder.setWithIntegrityPacket(withIntegrityCheck);
        builder.setSecureRandom(new SecureRandom());

        PGPEncryptedDataGenerator cpk = new PGPEncryptedDataGenerator(builder);
        cpk.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(pubKey));

        byte[] bytes = bOut.toByteArray();
        OutputStream cOut = cpk.open(outputStream, bytes.length);
        cOut.write(bytes);
        cOut.close();
        outputStream.close();

        return new String(encOut.toByteArray(), StandardCharsets.UTF_8);
    }

    public String encryptAndSignFile(String inputFile, boolean withIntegrityCheck, String purhapse) {
        getProvider();
        InputStream input = IOUtils.toInputStream(inputFile);
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        try {
            int DEFAULT_BUFFER_SIZE = 16 * 1024;

            PGPSecretKey pgpSec = BCPGPUtils.readSecretKey(privateKeyPath);
            PGPPrivateKey signingKey = getPrivateKey(purhapse);

            PGPPublicKey publicKey = pgpSec.getPublicKey();
            String userid = (String) publicKey.getUserIDs().next();

            BcPGPDataEncryptorBuilder dataEncryptor = new BcPGPDataEncryptorBuilder(PGPEncryptedData.AES_256);
            dataEncryptor.setWithIntegrityPacket(withIntegrityCheck);
            dataEncryptor.setSecureRandom(new SecureRandom());

            PGPEncryptedDataGenerator encryptedDataGenerator = new PGPEncryptedDataGenerator(dataEncryptor);
            encryptedDataGenerator.addMethod((new BcPublicKeyKeyEncryptionMethodGenerator(getPublicKey())));

            OutputStream finalOut = new BufferedOutputStream(new ArmoredOutputStream(output), DEFAULT_BUFFER_SIZE);
            OutputStream encOut = encryptedDataGenerator.open(finalOut, new byte[DEFAULT_BUFFER_SIZE]);

            PGPCompressedDataGenerator compressedDataGenerator = new PGPCompressedDataGenerator(PGPCompressedData.ZIP);
            BufferedOutputStream compressedOut = new BufferedOutputStream(compressedDataGenerator.open(encOut));

            PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(new BcPGPContentSignerBuilder(
                    publicKey.getAlgorithm(), HashAlgorithmTags.SHA256));
            signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, signingKey);

            PGPSignatureSubpacketGenerator pgpSignatureSubpacketGenerator = new PGPSignatureSubpacketGenerator();
            pgpSignatureSubpacketGenerator.setSignerUserID(false, userid);
            signatureGenerator.setHashedSubpackets(pgpSignatureSubpacketGenerator.generate());

            PGPOnePassSignature onePassSignature = signatureGenerator.generateOnePassVersion(false);
            onePassSignature.encode(compressedOut);

            PGPLiteralDataGenerator literalDataGenerator = new PGPLiteralDataGenerator(true);
            OutputStream literalOut = literalDataGenerator.open(compressedOut, PGPLiteralData.BINARY,
                    PGPLiteralData.CONSOLE, PGPLiteralData.NOW, new byte[1 << 16]);

            byte[] buffer = new byte[1 << 16];

            int bytesRead;
            while ((bytesRead = input.read(buffer)) != -1) {
                literalOut.write(buffer, 0, bytesRead);
                signatureGenerator.update(buffer, 0, bytesRead);
                literalOut.flush();
            }
            // Close Literal data stream and add signature
            literalOut.close();
            literalDataGenerator.close();
            signatureGenerator.generate().encode(compressedOut);
            // Close all other streams
            compressedOut.close();
            compressedDataGenerator.close();
            encOut.close();
            encryptedDataGenerator.close();
            finalOut.close();

            input.close();
            return new String(output.toByteArray(), StandardCharsets.UTF_8);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

四、编写解密类

package com.bop.util.pgp;

import lombok.SneakyThrows;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPCompressedData;
import org.bouncycastle.openpgp.PGPEncryptedDataList;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPOnePassSignature;
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureList;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
import org.bouncycastle.util.io.Streams;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.Provider;
import java.security.Security;
import java.util.Iterator;

/**
 * PGP 解密类
 *
 * @author neal.armstrong
 * @version 1.0
 * @since 2021.11.22
 */
public class PgpMemDecryptor {
    private final String publicKeyPath;
    private final String privateKeyPath;

    public PgpMemDecryptor(String publicKeyPath, String privateKeyPath) {
        this.publicKeyPath = publicKeyPath;
        this.privateKeyPath = privateKeyPath;
    }

    private InputStream PUBLIC_KEY;
    private InputStream PRIVATE_KEY;

//    int compressionAlgorithm;
//    int hashAlgorithm;
//    int symmetricKeyAlgorithm;

    private static void getProvider() {
        Provider provider = Security.getProvider("BC");
        if (provider == null) {
            provider = new BouncyCastleProvider();
            Security.addProvider(provider);
        }
    }

    @SneakyThrows
    private InputStream getPublicIs() {
        if (PUBLIC_KEY == null) {
            PUBLIC_KEY = Files.newInputStream(Paths.get(publicKeyPath));
        }
        return PUBLIC_KEY;
    }

    @SneakyThrows
    private InputStream getPrivateIs() {
        if (PRIVATE_KEY == null) {
            PRIVATE_KEY = Files.newInputStream(Paths.get(privateKeyPath));
        }
        return PRIVATE_KEY;
    }

    public String decryptAndVerifyFile(String content, String passPhrase) {
        getProvider();
        InputStream verifyKeyInput = getPrivateIs();
        InputStream decryptKeyInput = getPublicIs();
        char[] passwd;
        try {
            passwd = passPhrase.toCharArray();
            InputStream input = PGPUtil.getDecoderStream(IOUtils.toInputStream(content));

            PGPObjectFactory pgpF = new PGPObjectFactory(input, new BcKeyFingerprintCalculator());
            PGPEncryptedDataList enc;

            Object o = pgpF.nextObject();

            if (o instanceof PGPEncryptedDataList) {
                enc = (PGPEncryptedDataList) o;
            } else {
                enc = (PGPEncryptedDataList) pgpF.nextObject();
            }

            Iterator<?> it = enc.getEncryptedDataObjects();
            PGPPrivateKey sKey = null;
            PGPPublicKeyEncryptedData pbe = null;
            PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(verifyKeyInput),
                    new BcKeyFingerprintCalculator());

            while (sKey == null && it.hasNext()) {
                pbe = (PGPPublicKeyEncryptedData) it.next();
                sKey = BCPGPUtils.findSecretKey(pgpSec, pbe.getKeyID(), passwd);
            }

            if (sKey == null) {
                throw new IllegalArgumentException("secret key for message not found.");
            }

            InputStream clear = pbe.getDataStream(new BcPublicKeyDataDecryptorFactory(sKey));
//            pbe.getSymmetricAlgorithm(new BcPublicKeyDataDecryptorFactory(sKey));

            PGPObjectFactory plainFact = new PGPObjectFactory(clear, new BcKeyFingerprintCalculator());

            Object message;

            PGPOnePassSignatureList onePassSignatureList = null;
            PGPSignatureList signatureList = null;
            PGPCompressedData compressedData;

            message = plainFact.nextObject();
            ByteArrayOutputStream actualOutput = new ByteArrayOutputStream();

            while (message != null) {
                if (message instanceof PGPCompressedData) {
                    compressedData = (PGPCompressedData) message;
                    plainFact = new PGPObjectFactory(compressedData.getDataStream(), new BcKeyFingerprintCalculator());
                    message = plainFact.nextObject();
//                    this.compressionAlgorithm = compressedData.getAlgorithm();
                }

                if (message instanceof PGPLiteralData) {
                    Streams.pipeAll(((PGPLiteralData) message).getInputStream(), actualOutput);
                } else if (message instanceof PGPOnePassSignatureList) {
                    onePassSignatureList = (PGPOnePassSignatureList) message;
                } else if (message instanceof PGPSignatureList) {
                    signatureList = (PGPSignatureList) message;
                }
                message = plainFact.nextObject();
            }
            actualOutput.close();
            PGPPublicKey publicKey;
            byte[] outputBytes = actualOutput.toByteArray();
            if (onePassSignatureList == null || signatureList == null) {
                throw new PGPException("Poor PGP. Signatures not found.");
            } else {
                for (int i = 0; i < onePassSignatureList.size(); i++) {
                    PGPOnePassSignature ops = onePassSignatureList.get(0);
                    PGPPublicKeyRingCollection pgpRing = new PGPPublicKeyRingCollection(
                            PGPUtil.getDecoderStream(decryptKeyInput), new BcKeyFingerprintCalculator());
                    publicKey = pgpRing.getPublicKey(ops.getKeyID());
                    if (publicKey != null) {
                        ops.init(new BcPGPContentVerifierBuilderProvider(), publicKey);
                        ops.update(outputBytes);
                        PGPSignature signature = signatureList.get(i);
                        if (ops.verify(signature)) {
//                            this.hashAlgorithm = ops.getHashAlgorithm();
                        }
                    }
                }

            }
            return new String(outputBytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public String decryptFile(String content, String passphrase)
            throws Exception {
        getProvider();
        InputStream fIn = IOUtils.toInputStream(content);
        InputStream in = PGPUtil.getDecoderStream(fIn);
        InputStream keyIn = getPrivateIs();
        char[] passPhrase = passphrase.toCharArray();
        PGPObjectFactory pgpF = new PGPObjectFactory(in, new BcKeyFingerprintCalculator());
        PGPEncryptedDataList enc;
        Object o = pgpF.nextObject();

        if (o instanceof PGPEncryptedDataList) {
            enc = (PGPEncryptedDataList) o;
        } else {
            enc = (PGPEncryptedDataList) pgpF.nextObject();
        }

        Iterator<PGPPublicKeyEncryptedData> it = enc.getEncryptedDataObjects();
        PGPPrivateKey sKey = null;
        PGPPublicKeyEncryptedData pbe = null;

        while (sKey == null && it.hasNext()) {
            pbe = it.next();
            sKey = BCPGPUtils.findPrivateKey(keyIn, pbe.getKeyID(), passPhrase);
        }
        InputStream clear = pbe.getDataStream(new BcPublicKeyDataDecryptorFactory(sKey));
//        this.symmetricKeyAlgorithm = pbe.getSymmetricAlgorithm(new BcPublicKeyDataDecryptorFactory(sKey));

        PGPObjectFactory pgpFact = new PGPObjectFactory(clear, new BcKeyFingerprintCalculator());

        o = pgpFact.nextObject();
        if (o instanceof PGPCompressedData) {
            PGPCompressedData cData = (PGPCompressedData) o;
            pgpFact = new PGPObjectFactory(cData.getDataStream(), new BcKeyFingerprintCalculator());
            o = pgpFact.nextObject();
        }

        if (o instanceof PGPLiteralData) {
            PGPLiteralData ld = (PGPLiteralData) o;
            InputStream unc = ld.getInputStream();
            return IOUtils.toString(unc);
        }
        return null;
    }
}

五、测试

请选择正确的用法,例如使用加签加密方法时,对应请使用验签解密方法。使用加密方法,则直接使用解密方法即可。

五、结尾

由于时间仓促,并未对代码进行优化,许多流文件,可能遗漏并未进行完美关闭,还请注意使用。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
PGP(Pretty Good Privacy)是一种加密通信协议,用于保护数据的机密性和完整性。在PGP中,公钥加密算法用于加密和解密数据。 以下是一个简单的PGP模拟公钥加解密的Python代码示例: ```python from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_OAEP # 生成公钥和私钥 key = RSA.generate(2048) private_key = key.export_key() public_key = key.publickey().export_key() # 加密函数 def encrypt(public_key, plaintext): key = RSA.import_key(public_key) cipher_rsa = PKCS1_OAEP.new(key) ciphertext = cipher_rsa.encrypt(plaintext) return ciphertext # 解密函数 def decrypt(private_key, ciphertext): key = RSA.import_key(private_key) cipher_rsa = PKCS1_OAEP.new(key) plaintext = cipher_rsa.decrypt(ciphertext) return plaintext # 测试加解密 plaintext = b'Hello, world!' ciphertext = encrypt(public_key, plaintext) decrypted = decrypt(private_key, ciphertext) print(f"Plaintext: {plaintext}") print(f"Ciphertext: {ciphertext}") print(f"Decrypted: {decrypted}") ``` 在这个例子中,我们使用了Python加密库`pycryptodome`中的RSA加密算法和PKCS#1 OAEP填充方式。我们首先生成了一个2048位的RSA密钥对,并将公钥和私钥导出为字符串。然后定义了一个加密函数和一个解密函数,分别使用RSA加密算法和PKCS#1 OAEP填充方式进行加密和解密。最后我们测试了加解密的功能,并打印出了结果。 需要注意的是,这只是一个简单的PGP模拟公钥加解密示例,实际使用PGP时还需要考虑密钥管理、数字签名等问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值