跳到主要内容

https 详解

· 阅读需 7 分钟
1adybug
子虚伊人

对称加密

对称加密是一种加密算法,其中加密和解密使用同一个密钥。它的工作原理是,发送方用密钥对数据进行加密,接收方使用相同的密钥来解密数据。由于加密和解密过程中使用的密钥是相同的,因此被称为“对称”。

对称加密的基本流程

  1. 密钥生成:系统生成一个共享的秘密密钥。
  2. 加密:发送方使用密钥和对称加密算法对原始数据进行加密,生成密文。
  3. 传输密文:将密文传输给接收方。
  4. 解密:接收方使用相同的密钥解密密文,恢复原始数据。

常见的对称加密算法

  • DES(数据加密标准):一种较早的对称加密算法,密钥长度为 56 位,安全性较低。
  • AES(高级加密标准):一种现代的对称加密算法,密钥长度可以是 128 位、192 位或 256 位,具有较高的安全性和效率。

对称加密的优点

  • 加密速度快:对称加密比非对称加密在处理大数据时速度更快。
  • 算法简单:实现相对简单,资源消耗较低。

对称加密的缺点

  • 密钥管理困难:双方必须安全地共享和管理密钥。如果密钥被泄露,数据就不再安全。客户端是不可信的,所以客户端的密钥泄露后果很严重。
  • 不适用于大量用户的环境:因为每对用户需要单独的一套密钥,管理多个密钥对会非常复杂。

非对称加密

非对称加密是一种加密算法,它使用一对密钥:公钥私钥。这对密钥中的一个用于加密,另一个则用于解密。公钥可以公开给任何人,而私钥必须由所有者保密。非对称加密解决了对称加密中“密钥分发”的难题,因为加密和解密使用不同的密钥。

非对称加密的基本流程

  1. 密钥对生成:系统生成一对密钥,包含一个公钥和一个私钥。
  2. 公钥加密:发送方使用接收方的公钥对消息进行加密。
  3. 私钥解密:接收方使用自己的私钥解密密文,恢复原始数据。

由于使用不同的密钥,加密和解密过程更加安全,尤其是在公钥公开的情况下,只有持有对应私钥的人才能解密密文。

公钥私钥是非对称加密算法中的两种密钥,它们共同构成一对密钥,用于加密和解密数据。这两个密钥具有特殊的数学关系:使用一个密钥加密的数据,必须用另一个密钥来解密。

公钥和私钥

公钥(Public Key)

  • 定义:公钥是非对称加密中的一个密钥,可以公开给任何人使用。
  • 用途:主要用于加密数据和验证数字签名。任何人都可以使用接收方的公钥对消息进行加密。
  • 特点:即使公钥是公开的,没有对应的私钥,外部的人也无法解密数据,因此公钥的公开不会带来风险。

私钥(Private Key)

  • 定义:私钥是与公钥成对的密钥,必须严格保密,仅由密钥所有者掌握。
  • 用途:用于解密通过公钥加密的数据,或者用来生成数字签名,证明消息的真实性。
  • 特点:只有持有私钥的人才能解密用公钥加密的消息,或者验证私钥签署的数字签名的真实性。

公钥和私钥的工作原理

  1. 加密和解密

    • 发送方使用接收方的公钥加密数据。
    • 接收方使用自己的私钥解密数据。

    由于公钥可以公开,任何人都能加密消息,但只有持有私钥的人能解密。

  2. 数字签名

    • 发送方使用自己的私钥对消息进行签名,证明消息是由自己发出的。
    • 接收方使用发送方的公钥验证签名,确保消息的真实性。

    这种机制确保了消息的完整性和发送者的身份,防止了数据篡改和伪造。

公钥和私钥的应用场景

  • 加密通信:使用接收方的公钥加密数据,确保只有接收方能使用私钥解密。
  • 数字签名和认证:使用私钥进行签名,确保接收方能够验证消息的来源和真实性。
  • 密钥交换:通过非对称加密安全地交换对称加密所用的密钥。

关键点总结

  • 公钥可以公开,主要用于加密和验证签名。
  • 私钥必须保密,主要用于解密和生成签名。
  • 公钥和私钥是互补的,一个加密的数据只能通过另一个来解密。

常见的非对称加密算法

  • RSA(Rivest-Shamir-Adleman):一种广泛使用的非对称加密算法,常用于数字签名和密钥交换。
  • ECC(椭圆曲线加密):一种效率更高的非对称加密算法,提供相同安全性所需的密钥长度更短,计算更快。

非对称加密的优点

  • 安全性更高:由于公钥可以公开,私钥可以保持私密,非对称加密解决了对称加密中的密钥分发问题。
  • 支持数字签名:可以验证消息发送者的身份,确保消息的完整性和真实性。

非对称加密的缺点

  • 加密速度较慢:相比对称加密,非对称加密在处理大量数据时速度较慢,计算复杂度高。
  • 密钥长度较长:为了提供相同的安全性,非对称加密所需的密钥通常比对称加密的密钥长得多。

HTTPS

HTTPS(超文本传输安全协议)使用了对称加密、非对称加密和数字证书来保证通信的安全性。这个过程大致可以分为以下几个步骤:

  1. 建立安全连接:

    当浏览器连接到 HTTPS 网站时,服务器会发送其 SSL 证书给浏览器。这个证书包含了服务器的公钥,以及由可信任的证书颁发机构(CA)签名的证书信息。

  2. 验证证书:

    浏览器会验证证书的有效性,包括检查证书是否过期、是否由可信的 CA 签发等。这一步骤可以确保浏览器连接到的确实是预期的服务器,而不是某个冒充的恶意服务器。

  3. 生成会话密钥:

    验证通过后,浏览器会生成一个随机的对称密钥(会话密钥)。然后使用服务器的公钥对这个会话密钥进行加密。

  4. 密钥交换:

    浏览器将加密后的会话密钥发送给服务器。服务器使用自己的私钥解密,获得会话密钥。

  5. 安全通信:

    此后,浏览器和服务器就可以使用这个共享的会话密钥,通过对称加密算法来加密它们之间的所有通信内容。

为什么要使用这些技术:

  1. 非对称加密(公钥加密):

    • 用于安全地交换对称密钥
    • 解决了密钥分发的问题
    • 但是计算速度较慢
  2. 对称加密:

    • 用于加密实际的通信内容
    • 计算速度快,适合大量数据的加密
  3. CA 证书:

    • 用于验证服务器的身份
    • 防止中间人攻击

通过结合使用这些技术,HTTPS 能够:

  • 确保通信的机密性(防止被窃听)
  • 保证数据的完整性(防止被篡改)
  • 验证通信双方的身份(防止身份伪造)

这种方式既保证了安全性,又兼顾了效率,是目前互联网上广泛使用的安全通信方式。

document 和 document.documentElement 之间的区别

· 阅读需 1 分钟
1adybug
子虚伊人

documentdocument.documentElement 确实有一些重要区别:

  1. 定义:

    • document 是整个 HTML 文档的根节点。
    • document.documentElementHTML 文档的根元素,通常是 <html> 标签。
  2. 层级:

    • documentDOM 树的顶层对象。
    • document.documentElementdocument 的直接子节点。
  3. 属性和方法:

    • document 包含许多特有的属性和方法,如 createElement(), getElementById() 等。
    • document.documentElement 主要继承自 Element 接口,拥有元素通用的属性和方法。
  4. 尺寸获取:

    • 获取视口大小时,通常使用 document.documentElement.clientWidth/clientHeight
    • document 本身没有这些尺寸属性。
  5. 滚动相关:

    • 处理页面滚动时,常用 document.documentElement.scrollTop/scrollLeft
    • 某些浏览器可能需要使用 document.body 而非 document.documentElement
  6. DOCTYPE:

    • document 包含 DOCTYPE 声明。
    • document.documentElement 不包含 DOCTYPE,仅从 <html> 开始。

React Native 和 Expo 开发中的问题汇总

· 阅读需 1 分钟
1adybug
子虚伊人

无法连接到开发服务器

注意

could not connect to the development server

解决方案:

  1. 在虚拟机中连接 WiFi
  2. 在 Android Studio 中将 GradleJava 位置设置为系统位置

项目编译失败

解决方案:

在 Android Studio 中打开 android 目录

动画暂停之后速度变慢

原因:

动画暂停之后再开始,会以设置的全部时间来完成剩余的进度

解决方案:

/** 初始值 */
const fromValue = 0

/** 重点值 */
const toValue = 100

/** 时间 */
const duration = 10000

/** 速度 */
const speed = (toValue - fromValue) / duration

/** 动画值 */
const translateX = useRef(new Animated.Value(fromValue)).current

/** 暂停值 */
const stopValue = useRef(fromValue)

/** 动画状态 */
const status = useRef(false)

function onClick() {
// 如果暂停值已经达到目标值,说明动画已经完成
if (stopValue.current === toValue) return

// 如果动画处于播放状态,暂停动画,并且将当前值赋予给暂停值
if (status.current) translateX.stopAnimation(value => (stopValue.current = value))
// 否则播放动画
else {
Animated.timing(translateX, {
toValue,
duration: (toValue - stopValue.current) / speed,
useNativeDriver: true,
easing: Easing.linear,
}).start(({ finished }) => finished && (stopValue.current = toValue))
}

status.current = !status.current
}

useForm 警告

· 阅读需 1 分钟
1adybug
子虚伊人

在使用 Ant DesignForm 表单时,经常会遇到这个警告:

注意

Warning: Instance created by `useForm` is not connected to any Form element. Forget to pass `form` prop?

如果确定传递了 form 属性,那么一般是由于调用 form 实例的方法时,Form 表单还没有被渲染的原因,常见于 Modal 中嵌套的 Form,解决办法为监听弹窗是否被打开:

useEffect(() => {
if (!open) return
form.setFieldsValue({})
}, [open, form])

在类的原型或者函数自身声明属性

· 阅读需 2 分钟
1adybug
子虚伊人

声明 class 的 prototype 上存在某个属性

  1. 接口扩展

    interface Person {
    sayName(): void
    }

    class Person {
    constructor(public name: string) {}
    }

    Person.prototype.sayName = function sayName() {
    console.log(this.name)
    }

    const person = new Person("Tom")

    person.sayName()
  2. 声明合并

    class Person {
    constructor(public name: string) {}
    }

    interface Person {
    sayName(): void
    }

    Person.prototype.sayName = function sayName() {
    console.log(this.name)
    }

    const person = new Person("Tom")

    person.sayName()

方法 1 和方法 2 看起来似乎只是 classinterface 的顺序区别,以下是 claude 给出的解释: 主要区别:

  1. 声明顺序:

    在接口扩展中,我们先声明接口,后声明类。 在声明合并中,我们先声明类,后声明接口。

  2. 语义和意图:

    接口扩展更像是在定义一个契约,然后让类去实现这个契约。 声明合并更像是在已有的类上添加额外的属性或方法。

  3. 使用场景:

    接口扩展通常用于你预先知道类需要实现某些属性或方法时。 声明合并通常用于你想要在已有的类上添加新的属性或方法,尤其是在处理第三方库或 legacy 代码时。

  4. 灵活性:

    声明合并提供了更大的灵活性,因为你可以在代码的不同部分逐步扩展类的定义。 接口扩展要求你在一个地方定义所有的接口成员。

  5. 代码组织:

    接口扩展可能导致更清晰的代码结构,因为所有的类型信息都在一个地方。 声明合并允许你在需要的地方添加类型信息,这在某些情况下可能更方便。

实际上,这两种方法在功能上是等价的。TypeScript 编译器会以相同的方式处理它们。选择使用哪种方法主要取决于你的代码风格偏好和具体的使用场景。

在实际开发中,声明合并(第二种方法)可能更常用,特别是在需要扩展现有类或处理第三方库时。但是,如果你正在从头开始设计一个新的类,使用接口扩展(第一种方法)可能会让你的意图更加清晰。

声明函数自身存在某些属性

现在可以直接声明属性了:

const Bar = () => console.log("Hello World")

Bar.color = "red"

console.log(Bar.color)

function Foo() {
console.log("Hello World")
}

Foo.color = "red"

console.log(Foo.color)

限制类的类型

· 阅读需 1 分钟
1adybug
子虚伊人

有的时候我们希望函数的参数能够是某一种类,有两种办法限制这种参数:

  1. 使用 typeof

    class Person {
    constructor(public name: string) {}
    }

    function createPerson(constructor: typeof Person) {
    return new constructor("Tom")
    }

    const person = createPerson(Person)
  2. 使用 new

    class Person {
    constructor(public name: string) {}
    }

    function createPerson(constructor: new (name: string) => Person) {
    return new constructor("Tom")
    }

    const person = createPerson(Person)

    或者,扩展一下:

    interface Animal {
    name: string
    }

    class Person {
    constructor(public name: string) {}
    }

    function createAnimal(constructor: new (name: string) => Animal) {
    return new constructor("Tom")
    }

    const person = createAnimal(Person)

这里我们可以学到,只要在一个函数类型的前面加一个 new 关键字,便变成了构造函数

useRequest 中的 cacheTime 和 staleTime

· 阅读需 2 分钟
1adybug
子虚伊人

cacheTime 表示缓存数据回收时间。默认为 5 分钟。如果设置为 -1, 则表示缓存数据永不过期

staleTime 表示缓存数据新鲜时间。默认为 0。在该时间间隔内,认为数据是新鲜的,不会重新发请求。如果设置为 -1,则表示数据永远新鲜

cacheTime 表示数据在全局的缓存时间,即使到期被销毁了,也不影响已经加载完成的请求,它们的 data 依然是有有效数据的,不会重新请求。但是如果有新的组件(请求)产生了,此时它 data 便无法从全局的缓存中读取数据,因此初始 dataundefined,会进行一次新的请求,在请求完成后会同步更新所有相同的 cacheKeyhooksdata

staleTime 表示数据的新鲜时间,在新鲜的时间内,产生了新的请求,不会真正地去请求,而是使用全局缓存中的数据,如果全局缓存中的数据不存在,依然会进行一次真正的请求,在请求完成后会同步更新所有相同的 cacheKeyhooksdata

cacheTimestaleTime 以请求完成那个配置为准,后续的更新也不会改变

git 起步

· 阅读需 1 分钟
1adybug
子虚伊人
  1. 设置用户名和邮箱

    git config --global user.name "Your Name"
    git config --global user.email "your_email@example.com"
  2. 生成 ssh 密钥

    ssh-keygen -t ed25519 -C "your_email@example.com"
  3. 设置代理

    git config --global http.proxy http://proxyusername:proxypassword@proxy.server.com:port
    git config --global https.proxy https://proxyusername:proxypassword@proxy.server.com:port

在 shell 中添加命令别名

· 阅读需 1 分钟
1adybug
子虚伊人
  1. 打开终端。

  2. 编辑你的 shell 配置文件。例如,如果你使用的是 bash,则可以编辑 ~/.bashrc 文件;如果你使用的是 zsh,则可以编辑 ~/.zshrc 文件。如果你使用的是 PowerShell,则可以编辑 $PROFILE 文件。你可以使用以下命令来打开文件:

    vi ~/.bashrc
    vi ~/.zshrc
    notepad $PROFILE
  3. 在文件的末尾添加以下行:

    alias p="pnpm"
    Set-Alias p pnpm
  4. 保存文件并退出编辑器。

  5. 你可以运行以下命令来重新加载你的 shell 配置文件使更改生效:

    source ~/.bashrc
    source ~/.zshrc
    . $PROFILE

现在,你可以在终端中使用 p 来代替 pnpm 运行命令。例如:

p install lodash

这样就会等同于运行 pnpm install lodash

当然在 PowerShell 中还可以使用函数来实现:

function p {
pnpm $args
}