Synology群晖NAS如何安装iperf3内网测速工具 非Docker运行模式

几经搜索,发现了群晖的Diagnosis Tool工具,里面有78个工具,其中就包括iperf3,方法很简单。

群晖NAS内网速度测试

  1. SSH登录群晖NAS,并切换到管理员权限
    $ sudo -i
  2. 安装Diagnosis Tool,
    $ sudo synogear install
  3. 运行
    $ /var/packages/DiagnosisTool/target/tool/iperf3 -c 192.168.188.1

群晖NAS外网速度测试

  • 运行:/var/packages/DiagnosisTool/target/tool/speedtest-cli.py
  • 会在结果可以找到Download: 138.89 Mbit/s;Upload: 63.01 Mbit/s(这是我千兆移动的测试结果)

如果不想使用synogear了, 一个命令就可移除:

$ sudo synogear remove

78个群晖的Diagnosis Tool工具命令行参考

/var/packages/DiagnosisTool/target/tool/addr2name
/var/packages/DiagnosisTool/target/tool/arping
/var/packages/DiagnosisTool/target/tool/ash
/var/packages/DiagnosisTool/target/tool/bash
/var/packages/DiagnosisTool/target/tool/cifsiostat
/var/packages/DiagnosisTool/target/tool/clockdiff
/var/packages/DiagnosisTool/target/tool/dig
/var/packages/DiagnosisTool/target/tool/domain_test.sh
/var/packages/DiagnosisTool/target/tool/file
/var/packages/DiagnosisTool/target/tool/fix_idmap.sh
/var/packages/DiagnosisTool/target/tool/free
/var/packages/DiagnosisTool/target/tool/gcore
/var/packages/DiagnosisTool/target/tool/gdb
/var/packages/DiagnosisTool/target/tool/gdbserver
/var/packages/DiagnosisTool/target/tool/iftop
/var/packages/DiagnosisTool/target/tool/iostat
/var/packages/DiagnosisTool/target/tool/iotop
/var/packages/DiagnosisTool/target/tool/iperf
/var/packages/DiagnosisTool/target/tool/iperf3
/var/packages/DiagnosisTool/target/tool/kill
/var/packages/DiagnosisTool/target/tool/killall
/var/packages/DiagnosisTool/target/tool/ldd
/var/packages/DiagnosisTool/target/tool/lsof
/var/packages/DiagnosisTool/target/tool/ltrace
/var/packages/DiagnosisTool/target/tool/mpstat
/var/packages/DiagnosisTool/target/tool/name2addr
/var/packages/DiagnosisTool/target/tool/ncat
/var/packages/DiagnosisTool/target/tool/ndisc6
/var/packages/DiagnosisTool/target/tool/nethogs
/var/packages/DiagnosisTool/target/tool/nfsiostat-sysstat
/var/packages/DiagnosisTool/target/tool/nmap
/var/packages/DiagnosisTool/target/tool/nping
/var/packages/DiagnosisTool/target/tool/nslookup
/var/packages/DiagnosisTool/target/tool/perf-check.py
/var/packages/DiagnosisTool/target/tool/pgrep
/var/packages/DiagnosisTool/target/tool/pidof
/var/packages/DiagnosisTool/target/tool/pidstat
/var/packages/DiagnosisTool/target/tool/ping6
/var/packages/DiagnosisTool/target/tool/ping
/var/packages/DiagnosisTool/target/tool/pkill
/var/packages/DiagnosisTool/target/tool/pmap
/var/packages/DiagnosisTool/target/tool/ps
/var/packages/DiagnosisTool/target/tool/pstree
/var/packages/DiagnosisTool/target/tool/pwdx
/var/packages/DiagnosisTool/target/tool/rarpd
/var/packages/DiagnosisTool/target/tool/rdisc6
/var/packages/DiagnosisTool/target/tool/rdisc
/var/packages/DiagnosisTool/target/tool/rltraceroute6
/var/packages/DiagnosisTool/target/tool/sa1
/var/packages/DiagnosisTool/target/tool/sa2
/var/packages/DiagnosisTool/target/tool/sadc
/var/packages/DiagnosisTool/target/tool/sadf
/var/packages/DiagnosisTool/target/tool/sar
/var/packages/DiagnosisTool/target/tool/sh
/var/packages/DiagnosisTool/target/tool/sid2ugid.sh
/var/packages/DiagnosisTool/target/tool/slabtop
/var/packages/DiagnosisTool/target/tool/sockstat
/var/packages/DiagnosisTool/target/tool/speedtest-cli.py
/var/packages/DiagnosisTool/target/tool/strace
/var/packages/DiagnosisTool/target/tool/sysctl
/var/packages/DiagnosisTool/target/tool/sysstat
/var/packages/DiagnosisTool/target/tool/tcpdump
/var/packages/DiagnosisTool/target/tool/tcpdump_wrapper
/var/packages/DiagnosisTool/target/tool/tcpspray6
/var/packages/DiagnosisTool/target/tool/tcptraceroute6
/var/packages/DiagnosisTool/target/tool/telnet
/var/packages/DiagnosisTool/target/tool/tload
/var/packages/DiagnosisTool/target/tool/top
/var/packages/DiagnosisTool/target/tool/tracepath
/var/packages/DiagnosisTool/target/tool/traceroute6
/var/packages/DiagnosisTool/target/tool/tracert6
/var/packages/DiagnosisTool/target/tool/uptime
/var/packages/DiagnosisTool/target/tool/vmstat
/var/packages/DiagnosisTool/target/tool/w
/var/packages/DiagnosisTool/target/tool/watch
/var/packages/DiagnosisTool/target/tool/zblacklist
/var/packages/DiagnosisTool/target/tool/zmap
/var/packages/DiagnosisTool/target/tool/ztee

参考链接


Java—bouncycastle支持国密SM2的公钥加密算法

java代码是依赖 BouncyCastle 类库,经修改此类库中的 SM2Engine 类的原码而来,用于支持 SM2 公钥加密算法,符合《GB/T 35276-2017: 信息安全技术 SM2密码算法使用规范》。

SM4 国标《GB/T 32907-2016 信息安全技术 SM4分组密码算法》。

可以使用 gmssl 工具进行交互测试(http://gmssl.org)

引入jar:

<dependency>
  <groupId>org.bouncycastle</groupId>
  <artifactId>bcprov-jdk15on</artifactId>
  <version>1.68</version>
</dependency>

加解密工具类:

import java.io.IOException;
import java.math.BigInteger;
import java.security.SecureRandom;

import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECKeyParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.math.ec.ECFieldElement;
import org.bouncycastle.math.ec.ECMultiplier;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.FixedPointCombMultiplier;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.BigIntegers;
import org.bouncycastle.util.Memoable;
import org.bouncycastle.util.Pack;


/**
 * 自义的 SM2 公钥加密、私钥解密引擎, 用于替换  BouncyCastle 中的 SM2Engine 的实现,
 * 可用于非 java 开发的系统之间交换数据时的公钥加密、私钥解密,完全符合 GM/T 以下两个标准:
 * <br/>
 * <li>《GM/T 0003.4-2012: SM2椭圆曲线公钥密钥算法:第4部分:公钥加密算法》</li>
 * <li>《GM/T 0009-2012: SM2密码算法使用规范》</li>
 * <br/>
 * <p/>
 * <li>1.加密密文默认为 C1||C3||C2, 输出内容为 ASN.1 编码。符合 《GM/T 0009-2012: SM2密码算法使用规范》 标准
 * </li>
 * <li>2.加密密文设置为 C1||C2||C3,则输出内容不是 ASN.1 编码。与 《GM/T 0009-2012 ...》 标准不兼容。</li>
 * <br/>
 * 建议可以使用 GmSSL 工具进行交互测试,请参考 {@code http://gmssl.org}
 * <p>
 * SM2 public key encryption engine - based on https://tools.ietf.org/html/draft-shen-sm2-ecdsa-02.
 *
 *
 * @author YangHongFeng
 * @since 2020/5/28 creation
 */
public class MySm2Engine {

    public enum Mode {
        C1C2C3, C1C3C2;
    }

    private final Digest digest;
    private final Mode mode;

    private boolean forEncryption;
    private ECKeyParameters ecKey;
    private ECDomainParameters ecParams;
    private int curveLength;
    private SecureRandom random;

    /***
     * 默认采用国标:C1||C3||C2
     */
    public MySm2Engine() {
        this(new SM3Digest(), Mode.C1C3C2);
    }

    public MySm2Engine(Mode mode) {
        this(new SM3Digest(), mode);
    }

    public MySm2Engine(Digest digest) {
        this(digest, Mode.C1C2C3);
    }

    public MySm2Engine(Digest digest, Mode mode) {
        if (mode == null) {
            throw new IllegalArgumentException("mode cannot be NULL");
        }
        this.digest = digest;
        this.mode = mode;
    }

    /**
     * 初始化
     * @param forEncryption true-公钥加密, false-私钥解密
     * @param param 密码参数,从中获取公或私钥、及椭圆曲线相关参数
     */
    public void init(boolean forEncryption, CipherParameters param) {
        this.forEncryption = forEncryption;

        if (forEncryption) {
            ParametersWithRandom rParam = (ParametersWithRandom) param;

            ecKey = (ECKeyParameters) rParam.getParameters();
            ecParams = ecKey.getParameters();

            ECPoint s = ((ECPublicKeyParameters) ecKey).getQ().multiply(ecParams.getH());
            if (s.isInfinity()) {
                throw new IllegalArgumentException("invalid key: [h]Q at infinity");
            }

            random = rParam.getRandom();
        } else {
            ecKey = (ECKeyParameters) param;
            ecParams = ecKey.getParameters();
        }

        curveLength = (ecParams.getCurve().getFieldSize() + 7) / 8;
    }

    /**
     * 进行加密、或解密
     * @param in
     * @param inOff
     * @param inLen
     * @return
     * @throws InvalidCipherTextException
     */
    public byte[] processBlock(
            byte[] in,
            int inOff,
            int inLen)
            throws IOException, InvalidCipherTextException {
        if (forEncryption) {
            return encrypt(in, inOff, inLen);
        } else {
            return decrypt(in, inOff, inLen);
        }
    }

    public int getOutputSize(int inputLen) {
        return (1 + 2 * curveLength) + inputLen + digest.getDigestSize();
    }

    protected ECMultiplier createBasePointMultiplier() {
        return new FixedPointCombMultiplier();
    }

    private byte[] encrypt(byte[] in, int inOff, int inLen)
            throws IOException {
        byte[] c2 = new byte[inLen];

        System.arraycopy(in, inOff, c2, 0, c2.length);

        ECMultiplier multiplier = createBasePointMultiplier();

        ECPoint c1P;
        ECPoint kPB;
        do {
            BigInteger k = nextK();

            c1P = multiplier.multiply(ecParams.getG(), k).normalize();
            // c1 = c1P.getEncoded(false);

            kPB = ((ECPublicKeyParameters) ecKey).getQ().multiply(k).normalize();

            kdf(digest, kPB, c2);
        }

        while (notEncrypted(c2, in, inOff));

        byte[] c3 = new byte[digest.getDigestSize()];

        addFieldElement(digest, kPB.getAffineXCoord());
        digest.update(in, inOff, inLen);
        addFieldElement(digest, kPB.getAffineYCoord());

        digest.doFinal(c3, 0);

        switch (mode) {
        case C1C3C2:
            // 2020/06/01 按国标组装为 ANS.1 编码
            final ASN1EncodableVector vector = new ASN1EncodableVector();
            vector.add(new ASN1Integer(c1P.getXCoord().toBigInteger()));
            vector.add(new ASN1Integer(c1P.getYCoord().toBigInteger()));
            vector.add(new DEROctetString(c3));
            vector.add(new DEROctetString(c2));
            return new DERSequence(vector).getEncoded();
        default:
            byte[] c1 = c1P.getEncoded(false);
            return Arrays.concatenate(c1, c2, c3);
        }
    }

    private byte[] decrypt(byte[] in, int inOff, int inLen)
            throws InvalidCipherTextException, IOException {

        ECPoint c1P; // = ecParams.getCurve().decodePoint(c1);
        byte[] inHash ;
        byte[] inCipherData;

        if (mode == Mode.C1C3C2) {
            // 2020/06/01 按国标 ANS.1 编码 进行解码
            ASN1InputStream inputStream = new ASN1InputStream(in);
            ASN1Sequence seq = (ASN1Sequence) inputStream.readObject();
            if (seq.size() != 4){
                throw new InvalidCipherTextException("invalid cipher text");
            }
            int index = 0;

            // C1 == XCoordinate 、YCoordinate
            BigInteger x = ((ASN1Integer) seq.getObjectAt(index ++)).getPositiveValue();
            // YCoordinate
            BigInteger y = ((ASN1Integer) seq.getObjectAt(index ++)).getPositiveValue();

            // XCoord 、YCoord ==> CEPoint (C1)
            c1P = ecParams.getCurve().createPoint(x, y);

            // HASH (C3)
            inHash = ((ASN1OctetString)seq.getObjectAt(index ++)).getOctets();

            // CipherText (C2)
            inCipherData = ((ASN1OctetString)seq.getObjectAt(index)).getOctets();
        } else {
            // C1
            byte[] c1 = new byte[curveLength * 2 + 1];
            System.arraycopy(in, inOff, c1, 0, c1.length);
            c1P = ecParams.getCurve().decodePoint(c1);

            // C2 == inCipherData
            int digestSize = this.digest.getDigestSize();
            inCipherData = new byte[inLen - c1.length - digestSize];
            System.arraycopy(in, inOff + c1.length, inCipherData, 0, inCipherData.length);

            // C3 == Hash
            inHash = new byte[digestSize];
            System.arraycopy(in, inOff + c1.length + inCipherData.length, inHash, 0, inHash.length);
        }

        // 解密 ==> inCipherData;
        ECPoint s = c1P.multiply(ecParams.getH());
        if (s.isInfinity())
        {
            throw new InvalidCipherTextException("[h]C1 at infinity");
        }
        c1P = c1P.multiply(((ECPrivateKeyParameters)ecKey).getD()).normalize();
        kdf(digest, c1P, inCipherData);

        // 动态计算已解密的明文的摘要并比较
        byte[] cipherDigest = new byte[digest.getDigestSize()];

        addFieldElement(digest, c1P.getAffineXCoord());
        digest.update(inCipherData, 0, inCipherData.length);
        addFieldElement(digest, c1P.getAffineYCoord());

        digest.doFinal(cipherDigest, 0);

        int check = 0;
        if (mode == Mode.C1C3C2)
        {
            for (int i = 0; i != cipherDigest.length; i++) {
                check |= cipherDigest[i] ^ inHash[i];
            }
        } else {
            for (int i = 0; i != cipherDigest.length; i++) {
                check |= cipherDigest[i] ^ inHash[i];
            }
        }

        // Arrays.fill(c1, (byte)0);
        Arrays.fill(cipherDigest, (byte)0);

        if (check != 0)
        {
            Arrays.fill(inCipherData, (byte)0);
            throw new InvalidCipherTextException("invalid cipher text");
        }

        // return c2;
        return inCipherData;
    }

    private boolean notEncrypted(byte[] encData, byte[] in, int inOff) {
        for (int i = 0; i != encData.length; i++) {
            if (encData[i] != in[inOff + i]) {
                return false;
            }
        }

        return true;
    }

    private void kdf(Digest digest, ECPoint c1, byte[] encData) {
        int digestSize = digest.getDigestSize();
        byte[] buf = new byte[Math.max(4, digestSize)];
        int off = 0;

        Memoable memo = null;
        Memoable copy = null;

        if (digest instanceof Memoable) {
            addFieldElement(digest, c1.getAffineXCoord());
            addFieldElement(digest, c1.getAffineYCoord());
            memo = (Memoable) digest;
            copy = memo.copy();
        }

        int ct = 0;

        while (off < encData.length) {
            if (memo != null) {
                memo.reset(copy);
            } else {
                addFieldElement(digest, c1.getAffineXCoord());
                addFieldElement(digest, c1.getAffineYCoord());
            }

            Pack.intToBigEndian(++ct, buf, 0);
            digest.update(buf, 0, 4);
            digest.doFinal(buf, 0);

            int xorLen = Math.min(digestSize, encData.length - off);
            xor(encData, buf, off, xorLen);
            off += xorLen;
        }
    }

    private void xor(byte[] data, byte[] kdfOut, int dOff, int dRemaining) {
        for (int i = 0; i != dRemaining; i++) {
            data[dOff + i] ^= kdfOut[i];
        }
    }

    private BigInteger nextK() {
        int qBitLength = ecParams.getN().bitLength();

        BigInteger k;
        do {
            k = BigIntegers.createRandomBigInteger(qBitLength, random);
        }
        while (k.equals(BigIntegers.ZERO) || k.compareTo(ecParams.getN()) >= 0);

        return k;
    }

    private void addFieldElement(Digest digest, ECFieldElement v) {
        byte[] p = BigIntegers.asUnsignedByteArray(curveLength, v.toBigInteger());

        digest.update(p, 0, p.length);
    }
}

工具类:

import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.util.encoders.Hex;

import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;


public class SM2Util {
    //椭圆曲线ECParameters ASN.1 结构
    private static final X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
    //椭圆曲线公钥或私钥的基本域参数。
    private static final ECParameterSpec ecDomainParameters = new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());

    /**
     * 公钥字符串转换为 BCECPublicKey 公钥对象
     *
     * @param pubKeyHex 64字节十六进制公钥字符串(如果公钥字符串为65字节首个字节为0x04:表示该公钥为非压缩格式,操作时需要删除)
     * @return BCECPublicKey SM2公钥对象
     */
    public static BCECPublicKey getECPublicKeyByPublicKeyHex(String pubKeyHex) {
        //截取64字节有效的SM2公钥(如果公钥首个字节为0x04)
        if (pubKeyHex.length() > 128) {
            pubKeyHex = pubKeyHex.substring(pubKeyHex.length() - 128);
        }
        //将公钥拆分为x,y分量(各32字节)
        String stringX = pubKeyHex.substring(0, 64);
        String stringY = pubKeyHex.substring(stringX.length());
        //将公钥x、y分量转换为BigInteger类型
        BigInteger x = new BigInteger(stringX, 16);
        BigInteger y = new BigInteger(stringY, 16);
        //通过公钥x、y分量创建椭圆曲线公钥规范
        ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(x9ECParameters.getCurve().createPoint(x, y), ecDomainParameters);
        //通过椭圆曲线公钥规范,创建出椭圆曲线公钥对象(可用于SM2加密及验签)
        return new BCECPublicKey("EC", ecPublicKeySpec, BouncyCastleProvider.CONFIGURATION);
    }


    /**
     * 私钥字符串转换为 BCECPrivateKey 私钥对象
     *
     * @param privateKeyHex: 32字节十六进制私钥字符串
     * @return BCECPrivateKey:SM2私钥对象
     */
    public static BCECPrivateKey getBCECPrivateKeyByPrivateKeyHex(String privateKeyHex) {
        //将十六进制私钥字符串转换为BigInteger对象
        BigInteger d = new BigInteger(privateKeyHex, 16);
        //通过私钥和私钥域参数集创建椭圆曲线私钥规范
        ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(d, ecDomainParameters);
        //通过椭圆曲线私钥规范,创建出椭圆曲线私钥对象(可用于SM2解密和签名)
        return new BCECPrivateKey("EC", ecPrivateKeySpec, BouncyCastleProvider.CONFIGURATION);
    }

    /**
     * SM2加密算法
     *
     * @param parameters 公钥参数
     * @param data       明文数据
     */
    public static String encrypt(ECPublicKeyParameters parameters, String data) throws InvalidCipherTextException, IOException {
        MySm2Engine sm2Engine = new MySm2Engine();
        sm2Engine.init(true, new ParametersWithRandom(parameters, new SecureRandom()));

        byte[] in = data.getBytes(StandardCharsets.UTF_8);
        return Hex.toHexString(sm2Engine.processBlock(in, 0, in.length));
    }

    /**
     * SM2加密算法
     *
     * @param publicKey 公钥
     * @param data      明文数据
     */
    public static String encrypt(PublicKey publicKey, String data) throws InvalidCipherTextException, IOException {
        ECPublicKeyParameters ecPublicKeyParameters = null;
        if (publicKey instanceof BCECPublicKey) {
            BCECPublicKey bcecPublicKey = (BCECPublicKey) publicKey;
            ECParameterSpec ecParameterSpec = bcecPublicKey.getParameters();
            ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),
                    ecParameterSpec.getG(), ecParameterSpec.getN());
            ecPublicKeyParameters = new ECPublicKeyParameters(bcecPublicKey.getQ(), ecDomainParameters);
        }

        return encrypt(ecPublicKeyParameters, data);
    }

    /**
     * SM2解密算法
     *
     * @param privateKey 私钥
     * @param cipherData 密文数据
     */
    public static String decrypt(PrivateKey privateKey, String cipherData) throws InvalidCipherTextException, IOException {
        byte[] cipherDataByte = Hex.decode(cipherData);

        BCECPrivateKey bcecPrivateKey = (BCECPrivateKey) privateKey;
        ECParameterSpec ecParameterSpec = bcecPrivateKey.getParameters();

        ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),
                ecParameterSpec.getG(), ecParameterSpec.getN());

        ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(bcecPrivateKey.getD(),
                ecDomainParameters);

        MySm2Engine sm2Engine = new MySm2Engine();
        sm2Engine.init(false, ecPrivateKeyParameters);

        byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length);
        return new String(arrayOfBytes, StandardCharsets.UTF_8);
    }

}

测试例子:

import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.ECGenParameterSpec;

public class sm2_demo {

    public static void main(String[] args) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidCipherTextException, IOException {

        final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
        // 获取一个椭圆曲线类型的密钥对生成器
        final KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
        // 使用SM2参数初始化生成器
        kpg.initialize(sm2Spec);
        // 获取密钥对
        KeyPair keyPair = kpg.generateKeyPair();
        PublicKey publicKey = keyPair.getPublic();
        PrivateKey privateKey = keyPair.getPrivate();
        String m = "lzc_SM2_demo";
        String data = SM2Util.encrypt(publicKey, m);
        System.out.println(data);
        String text = SM2Util.decrypt(privateKey, data);
        System.out.println(text);
    }
}

参考链接


为容器添加基于BoxDecoration的labelText (作为TextField)

TextField 有一个很好的方法来在它的 BoxDecoration 装饰上放置一个文本标签,如下:

通过以下方式:

TextField(
        onTap: onTap,
        controller: controller,
        decoration: InputDecoration(
          labelText: "XP",
          border: OutlineInputBorder(
            borderRadius: BorderRadius.circular(50.0),
          ),
        ));

有没有办法在 Container 的其他 BoxDecoration 上实现同样的效果?例如,我想指定标签"XP":

        Container(
          decoration: BoxDecoration(
            borderRadius: BorderRadius.all(Radius.circular(5)),
            border: Border.all(color: color, width: 2.0),
            labelText: Text("XP"),   // No such attribute
          ),
          child: child,
        ),

但是 Flutter 没有为 BoxDecoration 提供 labelText (仅存在于 InputDecoration )。

我们可以使用 InputDecorator 作为父对象来完成上述需求,如下:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const Home(),
      debugShowCheckedModeBanner: false,
    );
  }
}

class Home extends StatefulWidget {
  const Home({super.key});

  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
          height: 100,
          width: 400,
          child: InputDecorator(
            decoration: InputDecoration(
                labelText: 'XP',
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.circular(10),
                )),
            child: Text("Container Content"),
          ),
        ),
      ),
    );
  }
}

输出

参考链接


Flutter TextField/TextFormField垂直方向完整填充父容器高度(或者全屏显示)

正常情况下,我们使用 TextField/TextFormField 的时候,都是单行的表单或者指定行数的情况。但是有时候,我们希望能自适应父容器的高度,或者高度动态自适应变化。

那么我们应该怎么操作呢?

继续阅读Flutter TextField/TextFormField垂直方向完整填充父容器高度(或者全屏显示)

基于 Verdaccio 搭建鸿蒙(HarmonyOS Next)开发的轻量级 Node.js 私有仓库

一、背景

最近在进行 HarmonyOS Next 应用开发,官方的 DevEco Studio 4.1 需要时候 Node.js,但是公司开发环境不支持外网访问,需要搭建内网的镜像服务器。下面,我们研究在内网服务器只使用 Apache(HTTPD)/Nginx 提供文件下载服务,不安装 NodeJs 搭建代理服务的方法来建立 NPM 文件下载代理。

执行缓存任务的设备是 MacBook Pro 2023 / macOS Sonoma 14.3

二、简介

1. 什么是 Verdaccio

“一个基于 Node.js 的轻量级私有仓库”。
平时使用 npm publish 进行发布时,上传的仓库默认地址是 npm,通过 Verdaccio 工具在本地新建一个仓库地址,再把本地的默认上传仓库地址切换到本地仓库地址即可。当 npm install 时没有找到本地的仓库,则 Verdaccio 默认配置中会从 npm 中央仓库下载。

注:Verdaccio 表示意大利中世纪晚期 fresco 绘画中流行的一种绿色的意思。

2. 优点
  • 私密性高,仅团队共享。
  • 安全性高,能够有效的防治恶意代码攻击。
  • 使用局域网,传输速度快。
3. 官网

三、准备环境

# 我们通过 nvm 管理 node 进行多版本切换 
$ brew install nvm

# 加载并且列出远程的 node 分支,否则执行 `nvm list` `nvm install` 等命令的
# 时候没办法列出或者安装对应的版本

$ nvm ls-remote

# 但是我们使用最新版本的 node 执行安装操作,这样才能干净的进行缓存
# 否则在我们后续安装 verdaccio 的时候,数据是已经缓存过的了
$ nvm install 20.11.0

# 没有特殊情况下,建议把刚刚安装的版本设置为默认版本
$ nvm alias default 20.11.0

# 安装 verdaccio
$ npm install --location=global verdaccio

# 全局安裝 npm 源管理工具(可以快速切换仓库源)

$ npm install --location=global nrm

# 添加一个私有 npm 源,'verdaccio' 为自定义的源地址名称 
$ nrm add verdaccio http://localhost:4873/

# 如果需要还原到默认设置,只需要删除当前用户下的 .nrmrc 文件即可

$ rm -rf .nrmrc

配置 verdaccio 从华为镜像服务器地址下载,默认配置服务器地址国内访问可能存在问题。另外注意禁用 npm-audit ,安全审计会非常非常慢,而且经常失败。

修改后的完整配置如下:

#
# This is the default configuration file. It allows all users to do anything,
# please read carefully the documentation and best practices to
# improve security.
#
# Look here for more config file examples:
# https://github.com/verdaccio/verdaccio/tree/5.x/conf
#
# Read about the best practices
# https://verdaccio.org/docs/best

# path to a directory with all packages
storage: /Users/xxxx/.local/share/verdaccio/storage
# path to a directory with plugins to include
plugins: ./plugins

# https://verdaccio.org/docs/webui
web:
  title: Verdaccio
  # comment out to disable gravatar support
  # gravatar: false
  # by default packages are ordercer ascendant (asc|desc)
  # sort_packages: asc
  # convert your UI to the dark side
  # darkMode: true
  # html_cache: true
  # by default all features are displayed
  # login: true
  # showInfo: true
  # showSettings: true
  # In combination with darkMode you can force specific theme
  # showThemeSwitch: true
  # showFooter: true
  # showSearch: true
  # showRaw: true
  # showDownloadTarball: true
  #  HTML tags injected after manifest <scripts/>
  # scriptsBodyAfter:
  #    - '<script type="text/javascript" src="https://my.company.com/customJS.min.js"></script>'
  #  HTML tags injected before ends </head>
  #  metaScripts:
  #    - '<script type="text/javascript" src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>'
  #    - '<script type="text/javascript" src="https://browser.sentry-cdn.com/5.15.5/bundle.min.js"></script>'
  #    - '<meta name="robots" content="noindex" />'
  #  HTML tags injected first child at <body/>
  #  bodyBefore:
  #    - '<div id="myId">html before webpack scripts</div>'
  #  Public path for template manifest scripts (only manifest)
  #  publicPath: http://somedomain.org/

# https://verdaccio.org/docs/configuration#authentication
auth:
  htpasswd:
    file: ./htpasswd
    # Maximum amount of users allowed to register, defaults to "+inf".
    # You can set this to -1 to disable registration.
    # max_users: 1000
    # Hash algorithm, possible options are: "bcrypt", "md5", "sha1", "crypt".
    # algorithm: bcrypt # by default is crypt, but is recommended use bcrypt for new installations
    # Rounds number for "bcrypt", will be ignored for other algorithms.
    # rounds: 10

# https://verdaccio.org/docs/configuration#uplinks
# a list of other known repositories we can talk to
uplinks:
  # npmjs:
  #   url: https://registry.npmjs.org/
  # 注意依赖顺序,一定要把 ohpm 放在最前面,先去 ohpm 更新,有些依赖,两个镜像上都存在,后面会出现冲突的情况
  ohpm:
    url: https://ohpm.openharmony.cn/ohpm/  
  ohpm2:
    url: https://repo.harmonyos.com/ohpm/
  oh_npm:
    url: https://repo.harmonyos.com/npm/
  npm_mirror:
    url: https://mirrors.huaweicloud.com/repository/npm/

# Learn how to protect your packages
# https://verdaccio.org/docs/protect-your-dependencies/
# https://verdaccio.org/docs/configuration#packages
packages:
  '@*/*':
    # scoped packages
    access: $all
    publish: $authenticated
    unpublish: $authenticated
    proxy: ohpm oh_npm npm_mirror

  '**':
    # allow all users (including non-authenticated users) to read and
    # publish all packages
    #
    # you can specify usernames/groupnames (depending on your auth plugin)
    # and three keywords: "$all", "$anonymous", "$authenticated"
    access: $all

    # allow all known users to publish/publish packages
    # (anyone can register by default, remember?)
    publish: $authenticated
    unpublish: $authenticated

    # if package is not available locally, proxy requests to 'npmjs' registry
    proxy: ohpm oh_npm npm_mirror

# To improve your security configuration and  avoid dependency confusion
# consider removing the proxy property for private packages
# https://verdaccio.org/docs/best#remove-proxy-to-increase-security-at-private-packages

# https://verdaccio.org/docs/configuration#server
# You can specify HTTP/1.1 server keep alive timeout in seconds for incoming connections.
# A value of 0 makes the http server behave similarly to Node.js versions prior to 8.0.0, which did not have a keep-alive timeout.
# WORKAROUND: Through given configuration you can workaround following issue https://github.com/verdaccio/verdaccio/issues/301. Set to 0 in case 60 is not enough.
server:
  keepAliveTimeout: 60
  # Allow `req.ip` to resolve properly when Verdaccio is behind a proxy or load-balancer
  # See: https://expressjs.com/en/guide/behind-proxies.html
  # trustProxy: '127.0.0.1'

# https://verdaccio.org/docs/configuration#offline-publish
# publish:
#   allow_offline: false

# https://verdaccio.org/docs/configuration#url-prefix
# url_prefix: /verdaccio/
# VERDACCIO_PUBLIC_URL='https://somedomain.org';
# url_prefix: '/my_prefix'
# // url -> https://somedomain.org/my_prefix/
# VERDACCIO_PUBLIC_URL='https://somedomain.org';
# url_prefix: '/'
# // url -> https://somedomain.org/
# VERDACCIO_PUBLIC_URL='https://somedomain.org/first_prefix';
# url_prefix: '/second_prefix'
# // url -> https://somedomain.org/second_prefix/'

# https://verdaccio.org/docs/configuration#security
# security:
#   api:
#     legacy: true
#     jwt:
#       sign:
#         expiresIn: 29d
#       verify:
#         someProp: [value]
#    web:
#      sign:
#        expiresIn: 1h # 1 hour by default
#      verify:
#         someProp: [value]

# https://verdaccio.org/docs/configuration#user-rate-limit
# userRateLimit:
#   windowMs: 50000
#   max: 1000

# https://verdaccio.org/docs/configuration#max-body-size
# max_body_size: 10mb

# https://verdaccio.org/docs/configuration#listen-port
# listen:
# - localhost:4873            # default value
# - http://localhost:4873     # same thing
# - 0.0.0.0:4873              # listen on all addresses (INADDR_ANY)
# - https://example.org:4873  # if you want to use https
# - "[::1]:4873"                # ipv6
# - unix:/tmp/verdaccio.sock    # unix socket

# The HTTPS configuration is useful if you do not consider use a HTTP Proxy
# https://verdaccio.org/docs/configuration#https
# https:
#   key: ./path/verdaccio-key.pem
#   cert: ./path/verdaccio-cert.pem
#   ca: ./path/verdaccio-csr.pem

# https://verdaccio.org/docs/configuration#proxy
# http_proxy: http://something.local/
# https_proxy: https://something.local/

# https://verdaccio.org/docs/configuration#notifications
# notify:
#   method: POST
#   headers: [{ "Content-Type": "application/json" }]
#   endpoint: https://usagge.hipchat.com/v2/room/3729485/notification?auth_token=mySecretToken
#   content: '{"color":"green","message":"New package published: * {{ name }}*","notify":true,"message_format":"text"}'

middlewares:
  audit:
    enabled: false

# https://verdaccio.org/docs/logger
# log settings
log: { type: stdout, format: pretty, level: http }
#experiments:
#  # support for npm token command
#  token: false
#  # disable writing body size to logs, read more on ticket 1912
#  bytesin_off: false
#  # enable tarball URL redirect for hosting tarball with a different server, the tarball_url_redirect can be a template string
#  tarball_url_redirect: 'https://mycdn.com/verdaccio/${packageName}/${filename}'
#  # the tarball_url_redirect can be a function, takes packageName and filename and returns the url, when working with a js configuration file
#  tarball_url_redirect(packageName, filename) {
#    const signedUrl = // generate a signed url
#    return signedUrl;
#  }

# translate your registry, api i18n not available yet
# i18n:
# list of the available translations https://github.com/verdaccio/verdaccio/blob/master/packages/plugins/ui-theme/src/i18n/ABOUT_TRANSLATIONS.md
#   web: en-US

启动一个独立的Shell 运行 verdaccio 

$ nvm use 20.11.0

$ verdaccio                                
 info --- config file  - /Users/xxxx/.config/verdaccio/config.yaml
 info --- the "crypt" algorithm is deprecated consider switch to "bcrypt" in the configuration file. Read the documentation for additional details
 info --- using htpasswd file: /Users/xxxx/.config/verdaccio/htpasswd
 info --- plugin successfully loaded: verdaccio-htpasswd
 info --- plugin successfully loaded: verdaccio-audit
 warn --- http address - http://localhost:4873/ - verdaccio/5.29.0

清理缓存,并且要求通过 verdaccio 代理下载:

$ nvm use 20.11.0

# 'verdaccio' 为添加源时定义的源地址名称
$ nrm use verdaccio

# 列出缓存目录路径
$ npm config ls -l | grep cache

# 清理缓存
$ npm cache clean --force

当前(2024/02/01)申请并通过了华为开发计划的才可以下载到 HarmonyOS NEXT 开发需要的 HUAWEI DevEco Studio 4.x 版本(API 11)HUAWEI DevEco Studio 5.x (API 11、API 12) HarmonyOS Developer管理中心套件货架 目前只有这个版本的包含离线鸿蒙开发依赖 ohpm-repo,官方文档以及报错信息还是稀烂,基本找不到有用信息,需要自己研究。

注意: 目前测试发现 DevEco Studio 4.1.3.500 版本无法真机调试 C++ 代码。 DevEco Studio 4.1.3.501 版本可以正常调试。

截止 2024/04/01 最新Release版本是 DevEco Studio 4.1.3.700,最新测试版本 DevEco Studio 5.0.3.100(SP1),可惜编译不通过,报错如下:

ERR_PNPM_NO_MATCHING_VERSION  No matching version found for @ohos/hvigor-ohos-plugin@4.2.0

DevEco Studio 4.1.3.501 使用的官方SDK下载地址:

下载完成后,解压缩到 SDK 目录下的 HarmonyOS-NEXT-DP1 目录即可,如下图:

继续阅读基于 Verdaccio 搭建鸿蒙(HarmonyOS Next)开发的轻量级 Node.js 私有仓库